diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 96a4de786..8f11fa57c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,7 +27,7 @@ jobs: - run: npm run build - run: npm run test:cov - name: SonarCloud Scan - uses: sonarsource/sonarqube-scan-action@v4.1.0 + uses: sonarsource/sonarqube-scan-action@v5 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/README.md b/README.md index 980350c04..a0a3a2b1f 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,12 @@ --- -[![npm version](https://badge.fury.io/js/dockview.svg)](https://www.npmjs.com/package/dockview) -[![npm](https://img.shields.io/npm/dm/dockview)](https://www.npmjs.com/package/dockview) +[![npm version](https://badge.fury.io/js/dockview-core.svg)](https://www.npmjs.com/package/dockview-core) +[![npm](https://img.shields.io/npm/dm/dockview-core)](https://www.npmjs.com/package/dockview-core) [![CI Build](https://github.com/mathuo/dockview/workflows/CI/badge.svg)](https://github.com/mathuo/dockview/actions?query=workflow%3ACI) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=mathuo_dockview&metric=coverage)](https://sonarcloud.io/summary/overall?id=mathuo_dockview) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=mathuo_dockview&metric=alert_status)](https://sonarcloud.io/summary/overall?id=mathuo_dockview) -[![Bundle Phobia](https://badgen.net/bundlephobia/minzip/dockview)](https://bundlephobia.com/result?p=dockview) +[![Bundle Phobia](https://badgen.net/bundlephobia/minzip/dockview-core)](https://bundlephobia.com/result?p=dockview-core) ## diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..d04d9fe59 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,8 @@ +# Reporting a Vulnerability + +- Dockview is an entirely open source project. +- All build and publication scripts use public Github Action files found [here](https://github.com/mathuo/dockview/tree/master/.github/workflows). +- All npm publications are verified through the use of [provenance statements](https://docs.npmjs.com/generating-provenance-statements/). +- All builds are scanned with SonarCube and outputs can be found [here](https://sonarcloud.io/summary/overall?id=mathuo_dockview). + +If you believe you have found a security or vulnerability issue please send a complete example to github.mathuo@gmail.com where it will be investigated. diff --git a/lerna.json b/lerna.json index c1fe88e6a..88b00f39a 100644 --- a/lerna.json +++ b/lerna.json @@ -2,7 +2,7 @@ "packages": [ "packages/*" ], - "version": "2.0.0", + "version": "4.0.0", "npmClient": "yarn", "command": { "publish": { diff --git a/package.json b/package.json index 3d17caa64..f9dcde43f 100644 --- a/package.json +++ b/package.json @@ -77,8 +77,5 @@ }, "engines": { "node": ">=18.0" - }, - "dependencies": { - "ag-grid-vue3": "^31.1.1" } } diff --git a/packages/dockview-angular/package.json b/packages/dockview-angular/package.json index cebda7271..63f6b6b58 100644 --- a/packages/dockview-angular/package.json +++ b/packages/dockview-angular/package.json @@ -1,6 +1,6 @@ { "name": "dockview-angular", - "version": "2.0.0", + "version": "4.0.0", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,6 +54,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage" }, "dependencies": { - "dockview-core": "^2.0.0" + "dockview-core": "^4.0.0" } } diff --git a/packages/dockview-core/package.json b/packages/dockview-core/package.json index 01e63a421..8fab699a9 100644 --- a/packages/dockview-core/package.json +++ b/packages/dockview-core/package.json @@ -1,6 +1,6 @@ { "name": "dockview-core", - "version": "2.0.0", + "version": "4.0.0", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", diff --git a/packages/dockview-core/src/__tests__/__mocks__/mockDockviewPanelModel.ts b/packages/dockview-core/src/__tests__/__mocks__/mockDockviewPanelModel.ts index 79e21b88a..2a43a1796 100644 --- a/packages/dockview-core/src/__tests__/__mocks__/mockDockviewPanelModel.ts +++ b/packages/dockview-core/src/__tests__/__mocks__/mockDockviewPanelModel.ts @@ -1,11 +1,12 @@ import { IDockviewPanelModel } from '../../dockview/dockviewPanelModel'; import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel'; import { - GroupPanelPartInitParameters, + TabPartInitParameters, IContentRenderer, ITabRenderer, } from '../../dockview/types'; import { PanelUpdateEvent } from '../../panel/types'; +import { TabLocation } from '../../dockview/framework'; export class DockviewPanelModelMock implements IDockviewPanelModel { constructor( @@ -17,8 +18,11 @@ export class DockviewPanelModelMock implements IDockviewPanelModel { // } + createTabRenderer(tabLocation: TabLocation): ITabRenderer { + return this.tab; + } - init(params: GroupPanelPartInitParameters): void { + init(params: TabPartInitParameters): void { // } diff --git a/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts b/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts index 0bc24eaa5..d4255ab22 100644 --- a/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts +++ b/packages/dockview-core/src/__tests__/api/dockviewPanelApi.spec.ts @@ -10,6 +10,7 @@ describe('groupPanelApi', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const panelMock = jest.fn(() => { @@ -50,6 +51,7 @@ describe('groupPanelApi', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const groupViewPanel = new DockviewGroupPanel( @@ -82,6 +84,7 @@ describe('groupPanelApi', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const groupViewPanel = new DockviewGroupPanel( diff --git a/packages/dockview-core/src/__tests__/dnd/droptarget.spec.ts b/packages/dockview-core/src/__tests__/dnd/droptarget.spec.ts index 2d82095ab..b150ba896 100644 --- a/packages/dockview-core/src/__tests__/dnd/droptarget.spec.ts +++ b/packages/dockview-core/src/__tests__/dnd/droptarget.spec.ts @@ -16,10 +16,10 @@ describe('droptarget', () => { beforeEach(() => { element = document.createElement('div'); - jest.spyOn(element, 'clientHeight', 'get').mockImplementation( + jest.spyOn(element, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 200); + jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 200); }); test('that dragover events are marked', () => { diff --git a/packages/dockview-core/src/__tests__/dockview/components/tab.spec.ts b/packages/dockview-core/src/__tests__/dockview/components/tab.spec.ts index cb246b598..4a2f0c72d 100644 --- a/packages/dockview-core/src/__tests__/dockview/components/tab.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/components/tab.spec.ts @@ -8,6 +8,7 @@ import { DockviewGroupPanel } from '../../../dockview/dockviewGroupPanel'; import { DockviewGroupPanelModel } from '../../../dockview/dockviewGroupPanelModel'; import { Tab } from '../../../dockview/components/tab/tab'; import { IDockviewPanel } from '../../../dockview/dockviewPanel'; +import { fromPartial } from '@total-typescript/shoehorn'; describe('tab', () => { test('that empty tab has inactive-tab class', () => { @@ -46,15 +47,10 @@ describe('tab', () => { id: 'testcomponentid', }; }); - const groupviewMock = jest.fn, []>( - () => { - return { - canDisplayOverlay: jest.fn(), - }; - } - ); - const groupView = new groupviewMock() as DockviewGroupPanelModel; + const groupView = fromPartial({ + canDisplayOverlay: jest.fn(), + }); const groupPanelMock = jest.fn, []>(() => { return { @@ -72,38 +68,33 @@ describe('tab', () => { groupPanel ); - jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation( + jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation( + jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation( () => 100 ); fireEvent.dragEnter(cut.element); fireEvent.dragOver(cut.element); - expect(groupView.canDisplayOverlay).toBeCalled(); + expect(groupView.canDisplayOverlay).toHaveBeenCalled(); expect( cut.element.getElementsByClassName('dv-drop-target-dropzone').length ).toBe(0); }); - test('that if you drag over yourself no drop target is shown', () => { + test('that if you drag over yourself a drop target is shown', () => { const accessorMock = jest.fn, []>(() => { return { id: 'testcomponentid', }; }); - const groupviewMock = jest.fn, []>( - () => { - return { - canDisplayOverlay: jest.fn(), - }; - } - ); - const groupView = new groupviewMock() as DockviewGroupPanelModel; + const groupView = fromPartial({ + canDisplayOverlay: jest.fn(), + }); const groupPanelMock = jest.fn, []>(() => { return { @@ -121,10 +112,10 @@ describe('tab', () => { groupPanel ); - jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation( + jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation( + jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation( () => 100 ); @@ -136,11 +127,11 @@ describe('tab', () => { fireEvent.dragEnter(cut.element); fireEvent.dragOver(cut.element); - expect(groupView.canDisplayOverlay).toBeCalledTimes(0); + expect(groupView.canDisplayOverlay).toHaveBeenCalledTimes(0); expect( cut.element.getElementsByClassName('dv-drop-target-dropzone').length - ).toBe(0); + ).toBe(1); }); test('that if you drag over another tab a drop target is shown', () => { @@ -175,10 +166,10 @@ describe('tab', () => { groupPanel ); - jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation( + jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation( + jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation( () => 100 ); @@ -229,10 +220,10 @@ describe('tab', () => { groupPanel ); - jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation( + jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation( + jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation( () => 100 ); @@ -289,10 +280,10 @@ describe('tab', () => { groupPanel ); - jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation( + jest.spyOn(cut.element, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(cut.element, 'clientWidth', 'get').mockImplementation( + jest.spyOn(cut.element, 'offsetWidth', 'get').mockImplementation( () => 100 ); 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 b6e4fa861..bb79949cf 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 @@ -10,6 +10,7 @@ import { fireEvent } from '@testing-library/dom'; import { TestPanel } from '../../dockviewGroupPanelModel.spec'; import { IDockviewPanel } from '../../../../dockview/dockviewPanel'; import { fromPartial } from '@total-typescript/shoehorn'; +import { DockviewPanelApi } from '../../../../api/dockviewPanelApi'; describe('tabsContainer', () => { test('that an external event does not render a drop target and calls through to the group mode', () => { @@ -17,6 +18,7 @@ describe('tabsContainer', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const groupviewMock = jest.fn, []>( @@ -41,16 +43,16 @@ describe('tabsContainer', () => { const emptySpace = cut.element .getElementsByClassName('dv-void-container') - .item(0); + .item(0) as HTMLElement; if (!emptySpace!) { fail('element not found'); } - jest.spyOn(emptySpace!, 'clientHeight', 'get').mockImplementation( + jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(emptySpace!, 'clientWidth', 'get').mockImplementation( + jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation( () => 100 ); @@ -70,17 +72,17 @@ describe('tabsContainer', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); - const groupviewMock = jest.fn, []>( - () => { - return { - canDisplayOverlay: jest.fn(), - }; - } - ); + const dropTargetContainer = document.createElement('div'); - const groupView = new groupviewMock() as DockviewGroupPanelModel; + const groupView = fromPartial({ + canDisplayOverlay: jest.fn(), + // dropTargetContainer: new DropTargetAnchorContainer( + // dropTargetContainer + // ), + }); const groupPanelMock = jest.fn, []>(() => { return { @@ -96,16 +98,16 @@ describe('tabsContainer', () => { const emptySpace = cut.element .getElementsByClassName('dv-void-container') - .item(0); + .item(0) as HTMLElement; if (!emptySpace!) { fail('element not found'); } - jest.spyOn(emptySpace!, 'clientHeight', 'get').mockImplementation( + jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(emptySpace!, 'clientWidth', 'get').mockImplementation( + jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation( () => 100 ); @@ -128,6 +130,10 @@ describe('tabsContainer', () => { expect( cut.element.getElementsByClassName('dv-drop-target-dropzone').length ).toBe(1); + // expect( + // dropTargetContainer.getElementsByClassName('dv-drop-target-anchor') + // .length + // ).toBe(1); }); test('that dropping over the empty space should render a drop target', () => { @@ -136,6 +142,7 @@ describe('tabsContainer', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const groupviewMock = jest.fn, []>( @@ -165,16 +172,16 @@ describe('tabsContainer', () => { const emptySpace = cut.element .getElementsByClassName('dv-void-container') - .item(0); + .item(0) as HTMLElement; if (!emptySpace!) { fail('element not found'); } - jest.spyOn(emptySpace!, 'clientHeight', 'get').mockImplementation( + jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(emptySpace!, 'clientWidth', 'get').mockImplementation( + jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation( () => 100 ); @@ -199,6 +206,7 @@ describe('tabsContainer', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const groupviewMock = jest.fn, []>( @@ -228,16 +236,16 @@ describe('tabsContainer', () => { const emptySpace = cut.element .getElementsByClassName('dv-void-container') - .item(0); + .item(0) as HTMLElement; if (!emptySpace!) { fail('element not found'); } - jest.spyOn(emptySpace!, 'clientHeight', 'get').mockImplementation( + jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(emptySpace!, 'clientWidth', 'get').mockImplementation( + jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation( () => 100 ); @@ -262,6 +270,7 @@ describe('tabsContainer', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const groupviewMock = jest.fn, []>( @@ -290,16 +299,16 @@ describe('tabsContainer', () => { const emptySpace = cut.element .getElementsByClassName('dv-void-container') - .item(0); + .item(0) as HTMLElement; if (!emptySpace!) { fail('element not found'); } - jest.spyOn(emptySpace!, 'clientHeight', 'get').mockImplementation( + jest.spyOn(emptySpace!, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(emptySpace!, 'clientWidth', 'get').mockImplementation( + jest.spyOn(emptySpace!, 'offsetWidth', 'get').mockImplementation( () => 100 ); @@ -330,6 +339,7 @@ describe('tabsContainer', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const groupPanelMock = jest.fn(() => { @@ -394,6 +404,7 @@ describe('tabsContainer', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const groupPanelMock = jest.fn(() => { @@ -460,6 +471,7 @@ describe('tabsContainer', () => { element: document.createElement('div'), addFloatingGroup: jest.fn(), doSetGroupActive: jest.fn(), + onDidOptionsChange: jest.fn(), }); const groupPanelMock = jest.fn(() => { @@ -516,6 +528,7 @@ describe('tabsContainer', () => { element: document.createElement('div'), addFloatingGroup: jest.fn(), doSetGroupActive: jest.fn(), + onDidOptionsChange: jest.fn(), }); const groupPanelMock = jest.fn(() => { @@ -567,6 +580,7 @@ describe('tabsContainer', () => { element: document.createElement('div'), addFloatingGroup: jest.fn(), getGroupPanel: jest.fn(), + onDidOptionsChange: jest.fn(), }); const groupPanelMock = jest.fn(() => { @@ -623,6 +637,7 @@ describe('tabsContainer', () => { element: document.createElement('div'), addFloatingGroup: jest.fn(), getGroupPanel: jest.fn(), + onDidOptionsChange: jest.fn(), }); const groupPanelMock = jest.fn(() => { @@ -690,6 +705,7 @@ describe('tabsContainer', () => { element: document.createElement('div'), addFloatingGroup: jest.fn(), getGroupPanel: jest.fn(), + onDidOptionsChange: jest.fn(), }); const groupPanelMock = jest.fn(() => { @@ -757,6 +773,7 @@ describe('tabsContainer', () => { element: document.createElement('div'), addFloatingGroup: jest.fn(), getGroupPanel: jest.fn(), + onDidOptionsChange: jest.fn(), }); const groupPanelMock = jest.fn(() => { @@ -815,4 +832,36 @@ describe('tabsContainer', () => { expect(result).toBeTruthy(); expect(result!.childNodes.length).toBe(0); }); + + test('class dv-single-tab is present when only one tab exists`', () => { + const cut = new TabsContainer( + fromPartial({ + options: {}, + onDidOptionsChange: jest.fn(), + }), + fromPartial({}) + ); + + expect(cut.element.classList.contains('dv-single-tab')).toBeFalsy(); + + const panel1 = new TestPanel( + 'panel_1', + fromPartial({}) + ); + cut.openPanel(panel1); + expect(cut.element.classList.contains('dv-single-tab')).toBeTruthy(); + + const panel2 = new TestPanel( + 'panel_2', + fromPartial({}) + ); + cut.openPanel(panel2); + expect(cut.element.classList.contains('dv-single-tab')).toBeFalsy(); + + cut.closePanel(panel1); + expect(cut.element.classList.contains('dv-single-tab')).toBeTruthy(); + + cut.closePanel(panel2); + expect(cut.element.classList.contains('dv-single-tab')).toBeFalsy(); + }); }); diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index cee519dbb..d84a723e4 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -133,116 +133,125 @@ describe('dockviewComponent', () => { }, className: 'test-a test-b', }); - expect(dockview.element.className).toBe('test-a test-b'); + expect(dockview.element.className).toBe( + 'test-a test-b dockview-theme-abyss' + ); dockview.updateOptions({ className: 'test-b test-c' }); - expect(dockview.element.className).toBe('test-b test-c'); + expect(dockview.element.className).toBe( + 'dockview-theme-abyss test-b test-c' + ); }); - // describe('memory leakage', () => { - // beforeEach(() => { - // window.open = () => fromPartial({ - // addEventListener: jest.fn(), - // close: jest.fn(), - // }); - // }); + describe('memory leakage', () => { + beforeEach(() => { + window.open = () => setupMockWindow(); + }); - // test('event leakage', () => { - // Emitter.setLeakageMonitorEnabled(true); + test('event leakage', async () => { + Emitter.setLeakageMonitorEnabled(true); - // dockview = new DockviewComponent({ - // parentElement: container, - // components: { - // default: PanelContentPartTest, - // }, - // }); + dockview = new DockviewComponent(container, { + createComponent(options) { + switch (options.name) { + case 'default': + return new PanelContentPartTest( + options.id, + options.name + ); + default: + throw new Error(`unsupported`); + } + }, + className: 'test-a test-b', + }); - // dockview.layout(500, 1000); + dockview.layout(500, 1000); - // const panel1 = dockview.addPanel({ - // id: 'panel1', - // component: 'default', - // }); + const panel1 = dockview.addPanel({ + id: 'panel1', + component: 'default', + }); - // const panel2 = dockview.addPanel({ - // id: 'panel2', - // component: 'default', - // }); + const panel2 = dockview.addPanel({ + id: 'panel2', + component: 'default', + }); - // dockview.removePanel(panel2); + dockview.removePanel(panel2); - // const panel3 = dockview.addPanel({ - // id: 'panel3', - // component: 'default', - // position: { - // direction: 'right', - // referencePanel: 'panel1', - // }, - // }); + const panel3 = dockview.addPanel({ + id: 'panel3', + component: 'default', + position: { + direction: 'right', + referencePanel: 'panel1', + }, + }); - // const panel4 = dockview.addPanel({ - // id: 'panel4', - // component: 'default', - // position: { - // direction: 'above', - // }, - // }); + const panel4 = dockview.addPanel({ + id: 'panel4', + component: 'default', + position: { + direction: 'above', + }, + }); - // dockview.moveGroupOrPanel( - // panel4.group, - // panel3.group.id, - // panel3.id, - // 'center' - // ); + panel4.api.group.api.moveTo({ + group: panel3.api.group, + position: 'center', + }); - // dockview.addPanel({ - // id: 'panel5', - // component: 'default', - // floating: true, - // }); + dockview.addPanel({ + id: 'panel5', + component: 'default', + floating: true, + }); - // const panel6 = dockview.addPanel({ - // id: 'panel6', - // component: 'default', - // position: { - // referencePanel: 'panel5', - // direction: 'within', - // }, - // }); + const panel6 = dockview.addPanel({ + id: 'panel6', + component: 'default', + position: { + referencePanel: 'panel5', + direction: 'within', + }, + }); - // dockview.addFloatingGroup(panel4.api.group); + dockview.addFloatingGroup(panel4.api.group); - // dockview.addPopoutGroup(panel6); + await dockview.addPopoutGroup(panel2); - // dockview.moveGroupOrPanel( - // panel1.group, - // panel6.group.id, - // panel6.id, - // 'center' - // ); + panel1.api.group.api.moveTo({ + group: panel6.api.group, + position: 'center', + }); - // dockview.moveGroupOrPanel( - // panel4.group, - // panel6.group.id, - // panel6.id, - // 'center' - // ); + panel4.api.group.api.moveTo({ + group: panel6.api.group, + position: 'center', + }); - // dockview.dispose(); + dockview.dispose(); - // 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'); - // } + if (Emitter.MEMORY_LEAK_WATCHER.size > 0) { + console.warn( + `${Emitter.MEMORY_LEAK_WATCHER.size} undisposed resources` + ); - // Emitter.setLeakageMonitorEnabled(false); - // }); - // }); + for (const entry of Array.from( + Emitter.MEMORY_LEAK_WATCHER.events + )) { + console.log('disposal', entry[1]); + } + throw new Error( + `${Emitter.MEMORY_LEAK_WATCHER.size} undisposed resources` + ); + } + + Emitter.setLeakageMonitorEnabled(false); + }); + }); test('duplicate panel', () => { dockview.layout(500, 1000); @@ -1097,7 +1106,9 @@ describe('dockviewComponent', () => { disposable.dispose(); }); - test('events flow', () => { + test('events flow', async () => { + window.open = () => setupMockWindow(); + dockview.layout(1000, 1000); let events: { @@ -1290,7 +1301,42 @@ describe('dockviewComponent', () => { expect(dockview.size).toBe(0); expect(dockview.totalPanels).toBe(0); + events = []; + + const panel8 = dockview.addPanel({ + id: 'panel8', + component: 'default', + }); + const panel9 = dockview.addPanel({ + id: 'panel9', + component: 'default', + floating: true, + }); + const panel10 = dockview.addPanel({ + id: 'panel10', + component: 'default', + }); + + expect(await dockview.addPopoutGroup(panel10)).toBeTruthy(); + + expect(events).toEqual([ + { type: 'ADD_GROUP', group: panel8.group }, + { type: 'ADD_PANEL', panel: panel8 }, + { type: 'ACTIVE_GROUP', group: panel8.group }, + { type: 'ACTIVE_PANEL', panel: panel8 }, + { type: 'ADD_GROUP', group: panel9.group }, + { type: 'ADD_PANEL', panel: panel9 }, + { type: 'ACTIVE_GROUP', group: panel9.group }, + { type: 'ACTIVE_PANEL', panel: panel9 }, + { type: 'ADD_PANEL', panel: panel10 }, + { type: 'ACTIVE_PANEL', panel: panel10 }, + { type: 'ADD_GROUP', group: panel10.group }, + ]); + + events = []; disposable.dispose(); + + expect(events.length).toBe(0); }); test('that removing a panel from a group reflects in the dockviewcomponent when searching for a panel', () => { @@ -2411,17 +2457,17 @@ describe('dockviewComponent', () => { const group = dockview.getGroupPanel('panel2')!.api.group; const viewQuery = group.element.querySelectorAll( - '.dv-groupview > .dv-tabs-and-actions-container > .dv-tabs-container > .dv-tab' + '.dv-groupview > .dv-tabs-and-actions-container > .dv-scrollable > .dv-tabs-container > .dv-tab' ); expect(viewQuery.length).toBe(2); const viewQuery2 = group.element.querySelectorAll( - '.dv-groupview > .dv-tabs-and-actions-container > .dv-tabs-container > .dv-tab > .dv-default-tab' + '.dv-groupview > .dv-tabs-and-actions-container > .dv-scrollable > .dv-tabs-container > .dv-tab > .dv-default-tab' ); expect(viewQuery2.length).toBe(1); const viewQuery3 = group.element.querySelectorAll( - '.dv-groupview > .dv-tabs-and-actions-container > .dv-tabs-container > .dv-tab > .panel-tab-part-panel2' + '.dv-groupview > .dv-tabs-and-actions-container > .dv-scrollable > .dv-tabs-container > .dv-tab > .panel-tab-part-panel2' ); expect(viewQuery3.length).toBe(1); }); @@ -3334,10 +3380,10 @@ describe('dockviewComponent', () => { position: { direction: 'right' }, }); - Object.defineProperty(dockview.element, 'clientWidth', { + Object.defineProperty(dockview.element, 'offsetWidth', { get: () => 100, }); - Object.defineProperty(dockview.element, 'clientHeight', { + Object.defineProperty(dockview.element, 'offsetHeight', { get: () => 100, }); @@ -3707,16 +3753,16 @@ describe('dockviewComponent', () => { floatingGroups: [ { data: { - views: ['panelB'], - activeView: 'panelB', + views: ['panelC'], + activeView: 'panelC', id: '3', }, position: { left: 0, top: 0, height: 100, width: 100 }, }, { data: { - views: ['panelC'], - activeView: 'panelC', + views: ['panelD'], + activeView: 'panelD', id: '4', }, position: { left: 0, top: 0, height: 100, width: 100 }, @@ -4867,6 +4913,335 @@ describe('dockviewComponent', () => { ); }); + test('deserailize popout with no reference group', async () => { + jest.useRealTimers(); + + const container = document.createElement('div'); + + window.open = () => setupMockWindow(); + + const dockview = new DockviewComponent(container, { + createComponent(options) { + switch (options.name) { + case 'default': + return new PanelContentPartTest( + options.id, + options.name + ); + default: + throw new Error(`unsupported`); + } + }, + }); + + dockview.layout(1000, 1000); + + dockview.fromJSON({ + activeGroup: 'group-1', + grid: { + root: { + type: 'branch', + data: [ + { + type: 'leaf', + data: { + views: ['panel1'], + id: 'group-1', + activeView: 'panel1', + }, + size: 1000, + }, + ], + size: 1000, + }, + height: 1000, + width: 1000, + orientation: Orientation.VERTICAL, + }, + popoutGroups: [ + { + data: { + views: ['panel2'], + id: 'group-2', + activeView: 'panel2', + }, + position: null, + }, + ], + panels: { + panel1: { + id: 'panel1', + contentComponent: 'default', + title: 'panel1', + }, + panel2: { + id: 'panel2', + contentComponent: 'default', + title: 'panel2', + }, + }, + }); + + await new Promise((resolve) => setTimeout(resolve, 0)); + + const panel2 = dockview.api.getPanel('panel2'); + + const windowObject = + panel2?.api.location.type === 'popout' + ? panel2?.api.location.getWindow() + : undefined; + + expect(windowObject).toBeTruthy(); + + windowObject!.close(); + }); + + test('grid -> floating -> popout -> popout closed', async () => { + const container = document.createElement('div'); + + window.open = () => setupMockWindow(); + + const dockview = new DockviewComponent(container, { + createComponent(options) { + switch (options.name) { + case 'default': + return new PanelContentPartTest( + options.id, + options.name + ); + default: + throw new Error(`unsupported`); + } + }, + }); + + dockview.layout(1000, 500); + + const panel1 = dockview.addPanel({ + id: 'panel_1', + component: 'default', + }); + + const panel2 = dockview.addPanel({ + id: 'panel_2', + component: 'default', + }); + + const panel3 = dockview.addPanel({ + id: 'panel_3', + component: 'default', + position: { direction: 'right' }, + }); + + expect(panel1.api.location.type).toBe('grid'); + expect(panel2.api.location.type).toBe('grid'); + expect(panel3.api.location.type).toBe('grid'); + + dockview.addFloatingGroup(panel2); + + expect(panel1.api.location.type).toBe('grid'); + expect(panel2.api.location.type).toBe('floating'); + expect(panel3.api.location.type).toBe('grid'); + + await dockview.addPopoutGroup(panel2); + + expect(panel1.api.location.type).toBe('grid'); + expect(panel2.api.location.type).toBe('popout'); + expect(panel3.api.location.type).toBe('grid'); + + const windowObject = + panel2.api.location.type === 'popout' + ? panel2.api.location.getWindow() + : undefined; + expect(windowObject).toBeTruthy(); + + windowObject!.close(); + + expect(panel1.api.location.type).toBe('grid'); + expect(panel2.api.location.type).toBe('floating'); + expect(panel3.api.location.type).toBe('grid'); + }); + + test('grid -> floating -> popout -> floating', async () => { + const container = document.createElement('div'); + + window.open = () => setupMockWindow(); + + const dockview = new DockviewComponent(container, { + createComponent(options) { + switch (options.name) { + case 'default': + return new PanelContentPartTest( + options.id, + options.name + ); + default: + throw new Error(`unsupported`); + } + }, + }); + + dockview.layout(1000, 500); + + const panel1 = dockview.addPanel({ + id: 'panel_1', + component: 'default', + }); + + const panel2 = dockview.addPanel({ + id: 'panel_2', + component: 'default', + }); + + const panel3 = dockview.addPanel({ + id: 'panel_3', + component: 'default', + position: { direction: 'right' }, + }); + + expect(panel1.api.location.type).toBe('grid'); + expect(panel2.api.location.type).toBe('grid'); + expect(panel3.api.location.type).toBe('grid'); + + dockview.addFloatingGroup(panel2.group); + + expect(panel1.api.location.type).toBe('floating'); + expect(panel2.api.location.type).toBe('floating'); + expect(panel3.api.location.type).toBe('grid'); + + await dockview.addPopoutGroup(panel2.group); + + expect(panel1.api.location.type).toBe('popout'); + expect(panel2.api.location.type).toBe('popout'); + expect(panel3.api.location.type).toBe('grid'); + + dockview.addFloatingGroup(panel2.group); + + expect(panel1.api.location.type).toBe('floating'); + expect(panel2.api.location.type).toBe('floating'); + expect(panel3.api.location.type).toBe('grid'); + + await dockview.addPopoutGroup(panel2.group); + + expect(panel1.api.location.type).toBe('popout'); + expect(panel2.api.location.type).toBe('popout'); + expect(panel3.api.location.type).toBe('grid'); + + panel2.group.api.moveTo({ group: panel3.group }); + + expect(panel1.api.location.type).toBe('grid'); + expect(panel2.api.location.type).toBe('grid'); + expect(panel3.api.location.type).toBe('grid'); + }); + + test('that panel is rendered when moving from popout to new group', async () => { + const container = document.createElement('div'); + + window.open = () => setupMockWindow(); + + const dockview = new DockviewComponent(container, { + createComponent(options) { + switch (options.name) { + case 'default': + return new PanelContentPartTest( + options.id, + options.name + ); + default: + throw new Error(`unsupported`); + } + }, + }); + + dockview.layout(1000, 500); + + const panel1 = dockview.addPanel({ + id: 'panel_1', + component: 'default', + }); + + const panel2 = dockview.addPanel({ + id: 'panel_2', + component: 'default', + }); + + const panel3 = dockview.addPanel({ + id: 'panel_3', + component: 'default', + renderer: 'always', + }); + + await dockview.addPopoutGroup(panel2); + panel2.api.moveTo({ group: panel1.api.group, position: 'right' }); + + // confirm panel is rendered on DOM + expect( + panel2.group.element.querySelectorAll( + '.dv-content-container > .testpanel-panel_2' + ).length + ).toBe(1); + + await dockview.addPopoutGroup(panel3); + panel3.api.moveTo({ group: panel1.api.group, position: 'right' }); + + // confirm panel is rendered to always overlay container + expect( + dockview.element.querySelectorAll( + '.dv-render-overlay > .testpanel-panel_3' + ).length + ).toBe(1); + expect( + panel2.group.element.querySelectorAll( + '.dv-content-container > .testpanel-panel_3' + ).length + ).toBe(0); + }); + + test('move popout group of 1 panel inside grid', async () => { + const container = document.createElement('div'); + + window.open = () => setupMockWindow(); + + const dockview = new DockviewComponent(container, { + createComponent(options) { + switch (options.name) { + case 'default': + return new PanelContentPartTest( + options.id, + options.name + ); + default: + throw new Error(`unsupported`); + } + }, + }); + + dockview.layout(1000, 500); + + const panel1 = dockview.addPanel({ + id: 'panel_1', + component: 'default', + }); + + const panel2 = dockview.addPanel({ + id: 'panel_2', + component: 'default', + }); + + const panel3 = dockview.addPanel({ + id: 'panel_3', + component: 'default', + position: { direction: 'right' }, + }); + + await dockview.addPopoutGroup(panel2); + + panel2.api.moveTo({ position: 'top', group: panel3.group }); + + expect(dockview.panels.length).toBe(3); + expect(dockview.groups.length).toBe(3); + }); + test('add a popout group', async () => { const container = document.createElement('div'); @@ -5071,7 +5446,7 @@ describe('dockviewComponent', () => { mockWindow.close(); expect(panel1.group.api.location.type).toBe('grid'); - expect(panel2.group.api.location.type).toBe('grid'); + expect(panel2.group.api.location.type).toBe('floating'); expect(panel3.group.api.location.type).toBe('grid'); dockview.clear(); @@ -5362,6 +5737,42 @@ describe('dockviewComponent', () => { }, ]); }); + + test('dispose of dockview instance when popup is open', async () => { + const container = document.createElement('div'); + + window.open = () => setupMockWindow(); + + const dockview = new DockviewComponent(container, { + createComponent(options) { + switch (options.name) { + case 'default': + return new PanelContentPartTest( + options.id, + options.name + ); + default: + throw new Error(`unsupported`); + } + }, + }); + + dockview.layout(1000, 500); + + dockview.addPanel({ + id: 'panel_1', + component: 'default', + }); + + const panel2 = dockview.addPanel({ + id: 'panel_2', + component: 'default', + }); + + expect(await dockview.addPopoutGroup(panel2.group)).toBeTruthy(); + + dockview.dispose(); + }); }); describe('maximized group', () => { @@ -6318,36 +6729,4 @@ describe('dockviewComponent', () => { expect(api.panels.length).toBe(3); expect(api.groups.length).toBe(3); }); - - describe('updateOptions', () => { - test('gap', () => { - const container = document.createElement('div'); - - const dockview = new DockviewComponent(container, { - createComponent(options) { - switch (options.name) { - case 'default': - return new PanelContentPartTest( - options.id, - options.name - ); - default: - throw new Error(`unsupported`); - } - }, - gap: 6, - }); - - expect(dockview.gap).toBe(6); - - dockview.updateOptions({ gap: 10 }); - expect(dockview.gap).toBe(10); - - dockview.updateOptions({}); - expect(dockview.gap).toBe(10); - - dockview.updateOptions({ gap: 15 }); - expect(dockview.gap).toBe(15); - }); - }); }); diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanel.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanel.spec.ts index bbebcfb89..1a01b1d30 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanel.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanel.spec.ts @@ -2,10 +2,12 @@ import { DockviewComponent } from '../../dockview/dockviewComponent'; import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel'; import { fromPartial } from '@total-typescript/shoehorn'; import { GroupOptions } from '../../dockview/dockviewGroupPanelModel'; -import { DockviewPanel } from '../../dockview/dockviewPanel'; +import { DockviewPanel, IDockviewPanel } from '../../dockview/dockviewPanel'; import { DockviewPanelModelMock } from '../__mocks__/mockDockviewPanelModel'; import { IContentRenderer, ITabRenderer } from '../../dockview/types'; import { OverlayRenderContainer } from '../../overlay/overlayRenderContainer'; +import { IDockviewPanelModel } from '../../dockview/dockviewPanelModel'; +import { ContentContainer } from '../../dockview/components/panel/content'; describe('dockviewGroupPanel', () => { test('default minimum/maximium width/height', () => { @@ -14,6 +16,7 @@ describe('dockviewGroupPanel', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), }); const options = fromPartial({}); const cut = new DockviewGroupPanel(accessor, 'test_id', options); @@ -24,6 +27,51 @@ describe('dockviewGroupPanel', () => { expect(cut.maximumWidth).toBe(Number.MAX_SAFE_INTEGER); }); + test('that onDidActivePanelChange is configured at inline', () => { + const accessor = fromPartial({ + onDidActivePanelChange: jest.fn(), + onDidAddPanel: jest.fn(), + onDidRemovePanel: jest.fn(), + options: {}, + api: {}, + renderer: 'always', + overlayRenderContainer: { + attach: jest.fn(), + detatch: jest.fn(), + }, + doSetGroupActive: jest.fn(), + onDidOptionsChange: jest.fn(), + }); + const options = fromPartial({}); + + const cut = new DockviewGroupPanel(accessor, 'test_id', options); + + let counter = 0; + + cut.api.onDidActivePanelChange((event) => { + counter++; + }); + + cut.model.openPanel( + fromPartial({ + updateParentGroup: jest.fn(), + view: { + tab: { element: document.createElement('div') }, + content: new ContentContainer(accessor, cut.model), + }, + api: { + renderer: 'onlyWhenVisible', + onDidTitleChange: jest.fn(), + onDidParametersChange: jest.fn(), + }, + layout: jest.fn(), + runEvents: jest.fn(), + }) + ); + + expect(counter).toBe(1); + }); + test('group constraints', () => { const accessor = fromPartial({ onDidActivePanelChange: jest.fn(), @@ -35,6 +83,7 @@ describe('dockviewGroupPanel', () => { detatch: jest.fn(), }), options: {}, + onDidOptionsChange: jest.fn(), }); const options = fromPartial({}); const cut = new DockviewGroupPanel(accessor, 'test_id', options); diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts index 55aed39ec..6e24cd53f 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts @@ -24,6 +24,7 @@ import { createOffsetDragOverEvent } from '../__test_utils__/utils'; import { OverlayRenderContainer } from '../../overlay/overlayRenderContainer'; import { Emitter } from '../../events'; import { fromPartial } from '@total-typescript/shoehorn'; +import { TabLocation } from '../../dockview/framework'; enum GroupChangeKind2 { ADD_PANEL, @@ -36,12 +37,16 @@ class TestModel implements IDockviewPanelModel { readonly contentComponent: string; readonly tab: ITabRenderer; - constructor(id: string) { + constructor(readonly id: string) { this.content = new TestHeaderPart(id); this.contentComponent = id; this.tab = new TestContentPart(id); } + createTabRenderer(tabLocation: TabLocation): ITabRenderer { + return new TestHeaderPart(this.id); + } + update(event: PanelUpdateEvent): void { // } @@ -265,6 +270,7 @@ describe('dockviewGroupPanelModel', () => { document.createElement('div'), fromPartial({}) ), + onDidOptionsChange: () => ({ dispose: jest.fn() }), }); groupview = new DockviewGroupPanel(dockview, 'groupview-1', options); @@ -646,6 +652,7 @@ describe('dockviewGroupPanelModel', () => { getPanel: jest.fn(), onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), + onDidOptionsChange: jest.fn(), }); const groupviewMock = jest.fn, []>( @@ -684,12 +691,12 @@ describe('dockviewGroupPanelModel', () => { const element = container .getElementsByClassName('dv-content-container') - .item(0)!; + .item(0)! as HTMLElement; - jest.spyOn(element, 'clientHeight', 'get').mockImplementation( + jest.spyOn(element, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100); + jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100); fireEvent.dragEnter(element); fireEvent.dragOver(element); @@ -708,6 +715,7 @@ describe('dockviewGroupPanelModel', () => { getPanel: jest.fn(), onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), + onDidOptionsChange: jest.fn(), }); const groupviewMock = jest.fn, []>( @@ -744,12 +752,12 @@ describe('dockviewGroupPanelModel', () => { const element = container .getElementsByClassName('dv-content-container') - .item(0)!; + .item(0)! as HTMLElement; - jest.spyOn(element, 'clientHeight', 'get').mockImplementation( + jest.spyOn(element, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100); + jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100); function run(value: number) { fireEvent.dragEnter(element); @@ -792,7 +800,7 @@ describe('dockviewGroupPanelModel', () => { fireEvent.dragEnd(element); }); - test('that should not show drop target if dropping on self', () => { + test('that should show drop target if dropping on self', () => { const accessor = fromPartial({ id: 'testcomponentid', options: {}, @@ -804,17 +812,12 @@ describe('dockviewGroupPanelModel', () => { document.createElement('div'), fromPartial({}) ), + onDidOptionsChange: jest.fn(), }); - const groupviewMock = jest.fn, []>( - () => { - return { - canDisplayOverlay: jest.fn(), - }; - } - ); - - const groupView = new groupviewMock() as DockviewGroupPanelModel; + const groupView = fromPartial({ + canDisplayOverlay: jest.fn(), + }); const groupPanelMock = jest.fn, []>(() => { return { @@ -842,12 +845,12 @@ describe('dockviewGroupPanelModel', () => { const element = container .getElementsByClassName('dv-content-container') - .item(0)!; + .item(0)! as HTMLElement; - jest.spyOn(element, 'clientHeight', 'get').mockImplementation( + jest.spyOn(element, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100); + jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100); LocalSelectionTransfer.getInstance().setData( [new PanelTransfer('testcomponentid', 'groupviewid', 'panel1')], @@ -861,10 +864,10 @@ describe('dockviewGroupPanelModel', () => { expect( element.getElementsByClassName('dv-drop-target-dropzone').length - ).toBe(0); + ).toBe(1); }); - test('that should not allow drop when dropping on self for same component id', () => { + test('that should allow drop when dropping on self for same component id', () => { const accessor = fromPartial({ id: 'testcomponentid', options: {}, @@ -876,6 +879,7 @@ describe('dockviewGroupPanelModel', () => { document.createElement('div'), fromPartial({}) ), + onDidOptionsChange: jest.fn(), }); const groupviewMock = jest.fn, []>( @@ -915,12 +919,12 @@ describe('dockviewGroupPanelModel', () => { const element = container .getElementsByClassName('dv-content-container') - .item(0)!; + .item(0) as HTMLElement; - jest.spyOn(element, 'clientHeight', 'get').mockImplementation( + jest.spyOn(element, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100); + jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100); LocalSelectionTransfer.getInstance().setData( [new PanelTransfer('testcomponentid', 'groupviewid', 'panel1')], @@ -934,7 +938,7 @@ describe('dockviewGroupPanelModel', () => { expect( element.getElementsByClassName('dv-drop-target-dropzone').length - ).toBe(0); + ).toBe(1); }); test('that should not allow drop when not dropping for different component id', () => { @@ -949,6 +953,7 @@ describe('dockviewGroupPanelModel', () => { document.createElement('div'), fromPartial({}) ), + onDidOptionsChange: jest.fn(), }); const groupviewMock = jest.fn, []>( @@ -988,12 +993,12 @@ describe('dockviewGroupPanelModel', () => { const element = container .getElementsByClassName('dv-content-container') - .item(0)!; + .item(0) as HTMLElement; - jest.spyOn(element, 'clientHeight', 'get').mockImplementation( + jest.spyOn(element, 'offsetHeight', 'get').mockImplementation( () => 100 ); - jest.spyOn(element, 'clientWidth', 'get').mockImplementation(() => 100); + jest.spyOn(element, 'offsetWidth', 'get').mockImplementation(() => 100); LocalSelectionTransfer.getInstance().setData( [new PanelTransfer('anothercomponentid', 'groupviewid', 'panel1')], diff --git a/packages/dockview-core/src/__tests__/gridview/baseComponentGridview.spec.ts b/packages/dockview-core/src/__tests__/gridview/baseComponentGridview.spec.ts index d8ed45209..3ab49faa7 100644 --- a/packages/dockview-core/src/__tests__/gridview/baseComponentGridview.spec.ts +++ b/packages/dockview-core/src/__tests__/gridview/baseComponentGridview.spec.ts @@ -105,6 +105,21 @@ class ClassUnderTest extends BaseGrid { } describe('baseComponentGridview', () => { + test('that the container is not removed when grid is disposed', () => { + const root = document.createElement('div'); + const container = document.createElement('div'); + root.appendChild(container); + + const cut = new ClassUnderTest(container, { + orientation: Orientation.HORIZONTAL, + proportionalLayout: true, + }); + + cut.dispose(); + + expect(container.parentElement).toBe(root); + }); + test('that .layout(...) force flag works', () => { const cut = new ClassUnderTest(document.createElement('div'), { orientation: Orientation.HORIZONTAL, diff --git a/packages/dockview-core/src/__tests__/gridview/gridviewComponent.spec.ts b/packages/dockview-core/src/__tests__/gridview/gridviewComponent.spec.ts index ec5f306f8..3063c692d 100644 --- a/packages/dockview-core/src/__tests__/gridview/gridviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/gridview/gridviewComponent.spec.ts @@ -36,7 +36,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, className: 'test-a test-b', }); @@ -51,7 +58,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -70,7 +84,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -100,7 +121,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -160,7 +188,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -287,7 +322,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(1000, 1000); @@ -323,7 +365,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -446,7 +495,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -468,14 +524,21 @@ describe('gridview', () => { gridview.dispose(); - expect(container.childNodes.length).toBe(0); + expect(container.children.length).toBe(0); }); test('#1/VERTICAL', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -533,7 +596,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -591,7 +661,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -667,7 +744,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -761,7 +845,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -855,7 +946,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -949,7 +1047,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -1073,7 +1178,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -1197,7 +1309,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -1323,7 +1442,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -1447,7 +1573,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.HORIZONTAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(800, 400); @@ -1571,7 +1704,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.fromJSON({ @@ -1698,7 +1838,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(1000, 1000); @@ -1728,7 +1875,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(1000, 1000); @@ -1757,7 +1911,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: false, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(1000, 1000); @@ -1795,7 +1956,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); let addGroup: GridviewPanel[] = []; @@ -1917,7 +2085,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(1600, 800); @@ -2043,7 +2218,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(6000, 5000); @@ -2318,7 +2500,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.VERTICAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); gridview.layout(5000, 6000); @@ -2591,7 +2780,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error(`unsupported panel '${options.name}'`); + } + }, }); let el = gridview.element.querySelector('.dv-view-container'); @@ -2655,9 +2851,7 @@ describe('gridview', () => { }, activePanel: 'panel_1', }); - }).toThrow( - "Cannot create 'panel_1', no component 'somethingBad' provided" - ); + }).toThrow("unsupported panel 'somethingBad'"); expect(gridview.groups.length).toBe(0); @@ -2670,7 +2864,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, }); expect(gridview.disableResizing).toBeFalsy(); @@ -2680,7 +2881,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, disableAutoResizing: true, }); @@ -2691,7 +2899,14 @@ describe('gridview', () => { const gridview = new GridviewComponent(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, - components: { default: TestGridview }, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestGridview(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, disableAutoResizing: true, }); gridview.layout(1000, 1000); diff --git a/packages/dockview-core/src/__tests__/gridview/gridviewPanel.spec.ts b/packages/dockview-core/src/__tests__/gridview/gridviewPanel.spec.ts index cfae5ea6b..18ed7d1d6 100644 --- a/packages/dockview-core/src/__tests__/gridview/gridviewPanel.spec.ts +++ b/packages/dockview-core/src/__tests__/gridview/gridviewPanel.spec.ts @@ -8,6 +8,7 @@ describe('gridviewPanel', () => { onDidAddPanel: jest.fn(), onDidRemovePanel: jest.fn(), options: {}, + onDidOptionsChange: jest.fn(), } as any; }); diff --git a/packages/dockview-core/src/__tests__/panel/componentFactory.spec.ts b/packages/dockview-core/src/__tests__/panel/componentFactory.spec.ts deleted file mode 100644 index 4e330056a..000000000 --- a/packages/dockview-core/src/__tests__/panel/componentFactory.spec.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { createComponent } from '../../panel/componentFactory'; - -describe('componentFactory', () => { - describe('createComponent', () => { - test('valid component and framework component', () => { - const mock = jest.fn(); - const mock2 = jest.fn(); - - expect(() => - createComponent( - 'id-1', - 'component-1', - { 'component-1': mock }, - { 'component-1': mock2 } - ) - ).toThrow( - "Cannot create 'id-1'. component 'component-1' registered as both a component and frameworkComponent" - ); - }); - - test('valid framework component but no factory', () => { - const mock = jest.fn(); - - expect(() => - createComponent( - 'id-1', - 'component-1', - {}, - { 'component-1': mock } - ) - ).toThrow( - "Cannot create 'id-1' for framework component 'component-1'. you must register a frameworkPanelWrapper to use framework components" - ); - }); - - test('valid framework component', () => { - const component = jest.fn(); - const createComponentFn = jest - .fn() - .mockImplementation(() => component); - const frameworkComponent = jest.fn(); - - expect( - createComponent( - 'id-1', - 'component-1', - {}, - { 'component-1': frameworkComponent }, - { - createComponent: createComponentFn, - } - ) - ).toBe(component); - - expect(createComponentFn).toHaveBeenCalledWith( - 'id-1', - 'component-1', - frameworkComponent - ); - }); - - test('no valid component with fallback', () => { - const mock = jest.fn(); - - expect( - createComponent( - 'id-1', - 'component-1', - {}, - {}, - { - createComponent: () => null, - }, - () => mock - ) - ).toBe(mock); - }); - - test('no valid component', () => { - expect(() => - createComponent('id-1', 'component-1', {}, {}) - ).toThrow( - "Cannot create 'id-1', no component 'component-1' provided" - ); - }); - - test('valid component', () => { - const component = jest.fn(); - - const componentResult = createComponent( - 'id-1', - 'component-1', - { 'component-1': component }, - {} - ); - - expect(component).toHaveBeenCalled(); - - expect(componentResult instanceof component).toBeTruthy(); - }); - }); -}); diff --git a/packages/dockview-core/src/__tests__/paneview/paneview.spec.ts b/packages/dockview-core/src/__tests__/paneview/paneview.spec.ts index b74c81e2b..5bc8720d8 100644 --- a/packages/dockview-core/src/__tests__/paneview/paneview.spec.ts +++ b/packages/dockview-core/src/__tests__/paneview/paneview.spec.ts @@ -1,14 +1,10 @@ import { CompositeDisposable } from '../../lifecycle'; import { Paneview } from '../../paneview/paneview'; -import { - IPaneBodyPart, - IPaneHeaderPart, - PaneviewPanel, -} from '../../paneview/paneviewPanel'; +import { IPanePart, PaneviewPanel } from '../../paneview/paneviewPanel'; import { Orientation } from '../../splitview/splitview'; class TestPanel extends PaneviewPanel { - protected getBodyComponent(): IPaneBodyPart { + protected getBodyComponent(): IPanePart { return { element: document.createElement('div'), update: () => { @@ -23,7 +19,7 @@ class TestPanel extends PaneviewPanel { }; } - protected getHeaderComponent(): IPaneHeaderPart { + protected getHeaderComponent(): IPanePart { return { element: document.createElement('div'), update: () => { diff --git a/packages/dockview-core/src/__tests__/paneview/paneviewComponent.spec.ts b/packages/dockview-core/src/__tests__/paneview/paneviewComponent.spec.ts index 1c5a77da7..a30f73fd5 100644 --- a/packages/dockview-core/src/__tests__/paneview/paneviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/paneview/paneviewComponent.spec.ts @@ -4,8 +4,7 @@ import { PanelUpdateEvent } from '../../panel/types'; import { PaneviewComponent } from '../../paneview/paneviewComponent'; import { PaneviewPanel, - IPaneBodyPart, - IPaneHeaderPart, + IPanePart, PanePanelComponentInitParameter, } from '../../paneview/paneviewPanel'; import { Orientation } from '../../splitview/splitview'; @@ -16,7 +15,7 @@ class TestPanel extends PaneviewPanel { } getHeaderComponent() { - return new (class Header implements IPaneHeaderPart { + return new (class Header implements IPanePart { private _element: HTMLElement = document.createElement('div'); get element() { @@ -38,7 +37,7 @@ class TestPanel extends PaneviewPanel { } getBodyComponent() { - return new (class Header implements IPaneBodyPart { + return new (class Header implements IPanePart { private _element: HTMLElement = document.createElement('div'); get element() { @@ -68,12 +67,39 @@ describe('componentPaneview', () => { container.className = 'container'; }); + test('that the container is not removed when grid is disposed', () => { + const root = document.createElement('div'); + const container = document.createElement('div'); + root.appendChild(container); + + const paneview = new PaneviewComponent(container, { + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, + }); + + paneview.dispose(); + + expect(container.parentElement).toBe(root); + expect(container.children.length).toBe(0); + }); + test('vertical panels', () => { const disposables = new CompositeDisposable(); const paneview = new PaneviewComponent(container, { - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); @@ -81,12 +107,12 @@ describe('componentPaneview', () => { paneview.addPanel({ id: 'panel1', - component: 'testPanel', + component: 'default', title: 'Panel 1', }); paneview.addPanel({ id: 'panel2', - component: 'testPanel', + component: 'default', title: 'Panel2', }); @@ -144,8 +170,13 @@ describe('componentPaneview', () => { test('serialization', () => { const paneview = new PaneviewComponent(container, { - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); @@ -156,7 +187,7 @@ describe('componentPaneview', () => { size: 1, data: { id: 'panel1', - component: 'testPanel', + component: 'default', title: 'Panel 1', }, expanded: true, @@ -165,7 +196,7 @@ describe('componentPaneview', () => { size: 2, data: { id: 'panel2', - component: 'testPanel', + component: 'default', title: 'Panel 2', }, expanded: false, @@ -174,7 +205,7 @@ describe('componentPaneview', () => { size: 3, data: { id: 'panel3', - component: 'testPanel', + component: 'default', title: 'Panel 3', }, }, @@ -220,7 +251,7 @@ describe('componentPaneview', () => { size: 756, data: { id: 'panel1', - component: 'testPanel', + component: 'default', title: 'Panel 1', }, expanded: true, @@ -230,7 +261,7 @@ describe('componentPaneview', () => { size: 22, data: { id: 'panel2', - component: 'testPanel', + component: 'default', title: 'Panel 2', }, expanded: false, @@ -240,7 +271,7 @@ describe('componentPaneview', () => { size: 22, data: { id: 'panel3', - component: 'testPanel', + component: 'default', title: 'Panel 3', }, expanded: false, @@ -252,20 +283,25 @@ describe('componentPaneview', () => { test('toJSON shouldnt fire any layout events', () => { const paneview = new PaneviewComponent(container, { - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); paneview.layout(1000, 1000); paneview.addPanel({ id: 'panel1', - component: 'testPanel', + component: 'default', title: 'Panel 1', }); paneview.addPanel({ id: 'panel2', - component: 'testPanel', + component: 'default', title: 'Panel 2', }); @@ -279,39 +315,15 @@ describe('componentPaneview', () => { disposable.dispose(); }); - test('dispose of paneviewComponent', () => { - expect(container.childNodes.length).toBe(0); - - const paneview = new PaneviewComponent(container, { - components: { - testPanel: TestPanel, - }, - }); - - paneview.layout(1000, 1000); - - paneview.addPanel({ - id: 'panel1', - component: 'testPanel', - title: 'Panel 1', - }); - paneview.addPanel({ - id: 'panel2', - component: 'testPanel', - title: 'Panel 2', - }); - - expect(container.childNodes.length).toBeGreaterThan(0); - - paneview.dispose(); - - expect(container.childNodes.length).toBe(0); - }); - test('panel is disposed of when component is disposed', () => { const paneview = new PaneviewComponent(container, { - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); @@ -319,12 +331,12 @@ describe('componentPaneview', () => { paneview.addPanel({ id: 'panel1', - component: 'testPanel', + component: 'default', title: 'Panel 1', }); paneview.addPanel({ id: 'panel2', - component: 'testPanel', + component: 'default', title: 'Panel 2', }); @@ -342,8 +354,13 @@ describe('componentPaneview', () => { test('panel is disposed of when removed', () => { const paneview = new PaneviewComponent(container, { - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); @@ -351,12 +368,12 @@ describe('componentPaneview', () => { paneview.addPanel({ id: 'panel1', - component: 'testPanel', + component: 'default', title: 'Panel 1', }); paneview.addPanel({ id: 'panel2', - component: 'testPanel', + component: 'default', title: 'Panel 2', }); @@ -374,8 +391,13 @@ describe('componentPaneview', () => { test('panel is disposed of when fromJSON is called', () => { const paneview = new PaneviewComponent(container, { - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); @@ -383,12 +405,12 @@ describe('componentPaneview', () => { paneview.addPanel({ id: 'panel1', - component: 'testPanel', + component: 'default', title: 'Panel 1', }); paneview.addPanel({ id: 'panel2', - component: 'testPanel', + component: 'default', title: 'Panel 2', }); @@ -406,8 +428,13 @@ describe('componentPaneview', () => { test('that fromJSON layouts are resized to the current dimensions', async () => { const paneview = new PaneviewComponent(container, { - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); @@ -420,7 +447,7 @@ describe('componentPaneview', () => { size: 1, data: { id: 'panel1', - component: 'testPanel', + component: 'default', title: 'Panel 1', }, expanded: true, @@ -429,7 +456,7 @@ describe('componentPaneview', () => { size: 2, data: { id: 'panel2', - component: 'testPanel', + component: 'default', title: 'Panel 2', }, expanded: true, @@ -438,7 +465,7 @@ describe('componentPaneview', () => { size: 3, data: { id: 'panel3', - component: 'testPanel', + component: 'default', title: 'Panel 3', }, expanded: true, @@ -454,7 +481,7 @@ describe('componentPaneview', () => { size: 122, data: { id: 'panel1', - component: 'testPanel', + component: 'default', title: 'Panel 1', }, expanded: true, @@ -464,7 +491,7 @@ describe('componentPaneview', () => { size: 122, data: { id: 'panel2', - component: 'testPanel', + component: 'default', title: 'Panel 2', }, expanded: true, @@ -474,7 +501,7 @@ describe('componentPaneview', () => { size: 356, data: { id: 'panel3', - component: 'testPanel', + component: 'default', title: 'Panel 3', }, expanded: true, @@ -486,8 +513,13 @@ describe('componentPaneview', () => { test('that disableAutoResizing is false by default', () => { const paneview = new PaneviewComponent(container, { - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); @@ -496,8 +528,13 @@ describe('componentPaneview', () => { test('that disableAutoResizing can be enabled', () => { const paneview = new PaneviewComponent(container, { - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, disableAutoResizing: true, }); @@ -507,8 +544,13 @@ describe('componentPaneview', () => { test('that setVisible toggles visiblity', () => { const paneview = new PaneviewComponent(container, { - components: { - default: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, disableAutoResizing: true, }); @@ -540,17 +582,22 @@ describe('componentPaneview', () => { test('update className', () => { const paneview = new PaneviewComponent(container, { - components: { - default: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, disableAutoResizing: true, className: 'test-a test-b', }); - expect(paneview.element.className).toBe('container test-a test-b'); + expect(paneview.element.className).toBe('test-a test-b'); paneview.updateOptions({ className: 'test-b test-c' }); - expect(paneview.element.className).toBe('container test-b test-c'); + expect(paneview.element.className).toBe('test-b test-c'); }); }); diff --git a/packages/dockview-core/src/__tests__/splitview/splitviewComponent.spec.ts b/packages/dockview-core/src/__tests__/splitview/splitviewComponent.spec.ts index 4c7ed46f3..6c034091c 100644 --- a/packages/dockview-core/src/__tests__/splitview/splitviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/splitview/splitviewComponent.spec.ts @@ -26,24 +26,52 @@ describe('componentSplitview', () => { container.className = 'container'; }); + test('that the container is not removed when grid is disposed', () => { + const root = document.createElement('div'); + const container = document.createElement('div'); + root.appendChild(container); + + const splitview = new SplitviewComponent(container, { + orientation: Orientation.VERTICAL, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } + }, + }); + + splitview.dispose(); + + expect(container.parentElement).toBe(root); + expect(container.children.length).toBe(0); + }); + test('event leakage', () => { Emitter.setLeakageMonitorEnabled(true); const splitview = new SplitviewComponent(container, { orientation: Orientation.VERTICAL, - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); splitview.layout(600, 400); const panel1 = splitview.addPanel({ id: 'panel1', - component: 'testPanel', + component: 'default', }); const panel2 = splitview.addPanel({ id: 'panel2', - component: 'testPanel', + component: 'default', }); splitview.movePanel(0, 1); @@ -67,15 +95,20 @@ describe('componentSplitview', () => { test('remove panel', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.VERTICAL, - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); splitview.layout(600, 400); - splitview.addPanel({ id: 'panel1', component: 'testPanel' }); - splitview.addPanel({ id: 'panel2', component: 'testPanel' }); - splitview.addPanel({ id: 'panel3', component: 'testPanel' }); + splitview.addPanel({ id: 'panel1', component: 'default' }); + splitview.addPanel({ id: 'panel2', component: 'default' }); + splitview.addPanel({ id: 'panel3', component: 'default' }); const panel1 = splitview.getPanel('panel1')!; const panel2 = splitview.getPanel('panel2')!; @@ -102,8 +135,13 @@ describe('componentSplitview', () => { test('horizontal dimensions', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.HORIZONTAL, - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); splitview.layout(600, 400); @@ -115,8 +153,13 @@ describe('componentSplitview', () => { test('vertical dimensions', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.VERTICAL, - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); splitview.layout(600, 400); @@ -128,15 +171,20 @@ describe('componentSplitview', () => { test('api resize', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.VERTICAL, - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); splitview.layout(400, 600); - splitview.addPanel({ id: 'panel1', component: 'testPanel' }); - splitview.addPanel({ id: 'panel2', component: 'testPanel' }); - splitview.addPanel({ id: 'panel3', component: 'testPanel' }); + splitview.addPanel({ id: 'panel1', component: 'default' }); + splitview.addPanel({ id: 'panel2', component: 'default' }); + splitview.addPanel({ id: 'panel3', component: 'default' }); const panel1 = splitview.getPanel('panel1')!; const panel2 = splitview.getPanel('panel2')!; @@ -180,13 +228,18 @@ describe('componentSplitview', () => { test('api', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.HORIZONTAL, - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); splitview.layout(600, 400); - splitview.addPanel({ id: 'panel1', component: 'testPanel' }); + splitview.addPanel({ id: 'panel1', component: 'default' }); const panel1 = splitview.getPanel('panel1'); @@ -197,7 +250,7 @@ describe('componentSplitview', () => { // expect(panel1?.api.isFocused).toBeFalsy(); expect(panel1!.api.isVisible).toBeTruthy(); - splitview.addPanel({ id: 'panel2', component: 'testPanel' }); + splitview.addPanel({ id: 'panel2', component: 'default' }); const panel2 = splitview.getPanel('panel2'); @@ -221,15 +274,20 @@ describe('componentSplitview', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.VERTICAL, - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); splitview.layout(300, 200); - splitview.addPanel({ id: 'panel1', component: 'testPanel' }); - splitview.addPanel({ id: 'panel2', component: 'testPanel' }); + splitview.addPanel({ id: 'panel1', component: 'default' }); + splitview.addPanel({ id: 'panel2', component: 'default' }); const panel1 = splitview.getPanel('panel1') as SplitviewPanel; const panel2 = splitview.getPanel('panel2') as SplitviewPanel; @@ -272,15 +330,20 @@ describe('componentSplitview', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.HORIZONTAL, - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); splitview.layout(300, 200); - splitview.addPanel({ id: 'panel1', component: 'testPanel' }); - splitview.addPanel({ id: 'panel2', component: 'testPanel' }); + splitview.addPanel({ id: 'panel1', component: 'default' }); + splitview.addPanel({ id: 'panel2', component: 'default' }); const panel1 = splitview.getPanel('panel1') as SplitviewPanel; const panel2 = splitview.getPanel('panel2') as SplitviewPanel; @@ -321,8 +384,13 @@ describe('componentSplitview', () => { test('serialization', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.VERTICAL, - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); splitview.layout(400, 6); @@ -331,15 +399,15 @@ describe('componentSplitview', () => { views: [ { size: 1, - data: { id: 'panel1', component: 'testPanel' }, + data: { id: 'panel1', component: 'default' }, snap: false, }, { size: 2, - data: { id: 'panel2', component: 'testPanel' }, + data: { id: 'panel2', component: 'default' }, snap: true, }, - { size: 3, data: { id: 'panel3', component: 'testPanel' } }, + { size: 3, data: { id: 'panel3', component: 'default' } }, ], size: 6, orientation: Orientation.VERTICAL, @@ -352,17 +420,17 @@ describe('componentSplitview', () => { views: [ { size: 1, - data: { id: 'panel1', component: 'testPanel' }, + data: { id: 'panel1', component: 'default' }, snap: false, }, { size: 2, - data: { id: 'panel2', component: 'testPanel' }, + data: { id: 'panel2', component: 'default' }, snap: true, }, { size: 3, - data: { id: 'panel3', component: 'testPanel' }, + data: { id: 'panel3', component: 'default' }, snap: false, }, ], @@ -375,8 +443,13 @@ describe('componentSplitview', () => { test('toJSON shouldnt fire any layout events', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.HORIZONTAL, - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); @@ -384,11 +457,11 @@ describe('componentSplitview', () => { splitview.addPanel({ id: 'panel1', - component: 'testPanel', + component: 'default', }); splitview.addPanel({ id: 'panel2', - component: 'testPanel', + component: 'default', }); const disposable = splitview.onDidLayoutChange(() => { @@ -401,39 +474,16 @@ describe('componentSplitview', () => { disposable.dispose(); }); - test('dispose of splitviewComponent', () => { - expect(container.childNodes.length).toBe(0); - - const splitview = new SplitviewComponent(container, { - orientation: Orientation.HORIZONTAL, - components: { - testPanel: TestPanel, - }, - }); - - splitview.layout(1000, 1000); - - splitview.addPanel({ - id: 'panel1', - component: 'testPanel', - }); - splitview.addPanel({ - id: 'panel2', - component: 'testPanel', - }); - - expect(container.childNodes.length).toBeGreaterThan(0); - - splitview.dispose(); - - expect(container.childNodes.length).toBe(0); - }); - test('panel is disposed of when component is disposed', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.HORIZONTAL, - components: { - default: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); @@ -463,8 +513,13 @@ describe('componentSplitview', () => { test('panel is disposed of when removed', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.HORIZONTAL, - components: { - default: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); @@ -494,8 +549,13 @@ describe('componentSplitview', () => { test('panel is disposed of when fromJSON is called', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.HORIZONTAL, - components: { - default: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); @@ -529,8 +589,13 @@ describe('componentSplitview', () => { test('that fromJSON layouts are resized to the current dimensions', async () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.VERTICAL, - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); splitview.layout(400, 600); @@ -539,15 +604,15 @@ describe('componentSplitview', () => { views: [ { size: 1, - data: { id: 'panel1', component: 'testPanel' }, + data: { id: 'panel1', component: 'default' }, snap: false, }, { size: 2, - data: { id: 'panel2', component: 'testPanel' }, + data: { id: 'panel2', component: 'default' }, snap: true, }, - { size: 3, data: { id: 'panel3', component: 'testPanel' } }, + { size: 3, data: { id: 'panel3', component: 'default' } }, ], size: 6, orientation: Orientation.VERTICAL, @@ -558,17 +623,17 @@ describe('componentSplitview', () => { views: [ { size: 100, - data: { id: 'panel1', component: 'testPanel' }, + data: { id: 'panel1', component: 'default' }, snap: false, }, { size: 200, - data: { id: 'panel2', component: 'testPanel' }, + data: { id: 'panel2', component: 'default' }, snap: true, }, { size: 300, - data: { id: 'panel3', component: 'testPanel' }, + data: { id: 'panel3', component: 'default' }, snap: false, }, ], @@ -581,8 +646,13 @@ describe('componentSplitview', () => { test('that disableAutoResizing is false by default', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.VERTICAL, - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); @@ -592,8 +662,13 @@ describe('componentSplitview', () => { test('that disableAutoResizing can be enabled', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.VERTICAL, - components: { - testPanel: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, disableAutoResizing: true, }); @@ -604,8 +679,13 @@ describe('componentSplitview', () => { test('that setVisible toggles visiblity', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.HORIZONTAL, - components: { - default: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, }); @@ -635,16 +715,21 @@ describe('componentSplitview', () => { test('update className', () => { const splitview = new SplitviewComponent(container, { orientation: Orientation.HORIZONTAL, - components: { - default: TestPanel, + createComponent: (options) => { + switch (options.name) { + case 'default': + return new TestPanel(options.id, options.name); + default: + throw new Error('unsupported'); + } }, className: 'test-a test-b', }); - expect(splitview.element.className).toBe('container test-a test-b'); + expect(splitview.element.className).toBe('test-a test-b'); splitview.updateOptions({ className: 'test-b test-c' }); - expect(splitview.element.className).toBe('container test-b test-c'); + expect(splitview.element.className).toBe('test-b test-c'); }); }); diff --git a/packages/dockview-core/src/api/component.api.ts b/packages/dockview-core/src/api/component.api.ts index 01bff2a8c..7f3c40299 100644 --- a/packages/dockview-core/src/api/component.api.ts +++ b/packages/dockview-core/src/api/component.api.ts @@ -38,9 +38,9 @@ import { DockviewGroupPanel, IDockviewGroupPanel, } from '../dockview/dockviewGroupPanel'; -import { Emitter, Event } from '../events'; +import { Event } from '../events'; import { IDockviewPanel } from '../dockview/dockviewPanel'; -import { PaneviewDropEvent } from '../paneview/draggablePaneviewPanel'; +import { PaneviewDidDropEvent } from '../paneview/draggablePaneviewPanel'; import { GroupDragEvent, TabDragEvent, @@ -51,7 +51,10 @@ import { DockviewWillDropEvent, WillShowOverlayLocationEvent, } from '../dockview/dockviewGroupPanelModel'; -import { PaneviewComponentOptions } from '../paneview/options'; +import { + PaneviewComponentOptions, + PaneviewDndOverlayEvent, +} from '../paneview/options'; import { SplitviewComponentOptions } from '../splitview/options'; import { GridviewComponentOptions } from '../gridview/options'; @@ -294,19 +297,12 @@ export class PaneviewApi implements CommonApi { /** * Invoked when a Drag'n'Drop event occurs that the component was unable to handle. Exposed for custom Drag'n'Drop functionality. */ - get onDidDrop(): Event { - const emitter = new Emitter(); + get onDidDrop(): Event { + return this.component.onDidDrop; + } - const disposable = this.component.onDidDrop((e) => { - emitter.fire({ ...e, api: this }); - }); - - emitter.dispose = () => { - disposable.dispose(); - emitter.dispose(); - }; - - return emitter.event; + get onUnhandledDragOverEvent(): Event { + return this.component.onUnhandledDragOverEvent; } constructor(private readonly component: IPaneviewComponent) {} @@ -633,9 +629,6 @@ export class DockviewApi implements CommonApi { return this.component.totalPanels; } - get gap(): number { - return this.component.gap; - } /** * Invoked when the active group changes. May be undefined if no group is active. @@ -918,10 +911,6 @@ export class DockviewApi implements CommonApi { return this.component.addPopoutGroup(item, options); } - setGap(gap: number | undefined): void { - this.component.updateOptions({ gap }); - } - updateOptions(options: Partial) { this.component.updateOptions(options); } diff --git a/packages/dockview-core/src/api/dockviewGroupPanelApi.ts b/packages/dockview-core/src/api/dockviewGroupPanelApi.ts index 12424debb..73b5a461b 100644 --- a/packages/dockview-core/src/api/dockviewGroupPanelApi.ts +++ b/packages/dockview-core/src/api/dockviewGroupPanelApi.ts @@ -6,7 +6,6 @@ import { DockviewGroupLocation, } from '../dockview/dockviewGroupPanelModel'; import { Emitter, Event } from '../events'; -import { MutableDisposable } from '../lifecycle'; import { GridviewPanelApi, GridviewPanelApiImpl } from './gridviewPanelApi'; export interface DockviewGroupMoveParams { @@ -41,8 +40,6 @@ const NOT_INITIALIZED_MESSAGE = 'dockview: DockviewGroupPanelApiImpl not initialized'; export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl { - private readonly _mutableDisposable = new MutableDisposable(); - private _group: DockviewGroupPanel | undefined; readonly _onDidLocationChange = @@ -50,8 +47,7 @@ export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl { readonly onDidLocationChange: Event = this._onDidLocationChange.event; - private readonly _onDidActivePanelChange = - new Emitter(); + readonly _onDidActivePanelChange = new Emitter(); readonly onDidActivePanelChange = this._onDidActivePanelChange.event; get location(): DockviewGroupLocation { @@ -66,8 +62,7 @@ export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl { this.addDisposables( this._onDidLocationChange, - this._onDidActivePanelChange, - this._mutableDisposable + this._onDidActivePanelChange ); } @@ -140,21 +135,6 @@ export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl { } initialize(group: DockviewGroupPanel): void { - /** - * TODO: Annoying initialization order caveat, find a better way to initialize and avoid needing null checks - * - * Due to the order on initialization we know that the model isn't defined until later in the same stack-frame of setup. - * By queuing a microtask we can ensure the setup is completed within the same stack-frame, but after everything else has - * finished ensuring the `model` is defined. - */ - this._group = group; - - queueMicrotask(() => { - this._mutableDisposable.value = - this._group!.model.onDidActivePanelChange((event) => { - this._onDidActivePanelChange.fire(event); - }); - }); } } diff --git a/packages/dockview-core/src/dnd/abstractDragHandler.ts b/packages/dockview-core/src/dnd/abstractDragHandler.ts index 84345c160..7ba701034 100644 --- a/packages/dockview-core/src/dnd/abstractDragHandler.ts +++ b/packages/dockview-core/src/dnd/abstractDragHandler.ts @@ -67,7 +67,7 @@ export abstract class DragHandler extends CompositeDisposable { * For example: in react-dnd if dataTransfer.types is not set then the dragStart event will be cancelled * through .preventDefault(). Since this is applied globally to all drag events this would break dockviews * dnd logic. You can see the code at - * https://github.com/react-dnd/react-dnd/blob/main/packages/backend-html5/src/HTML5BackendImpl.ts#L542 + P * https://github.com/react-dnd/react-dnd/blob/main/packages/backend-html5/src/HTML5BackendImpl.ts#L542 */ event.dataTransfer.setData('text/plain', ''); } @@ -75,7 +75,9 @@ export abstract class DragHandler extends CompositeDisposable { }), addDisposableListener(this.el, 'dragend', () => { this.pointerEventsDisposable.dispose(); - this.dataDisposable.dispose(); + setTimeout(() => { + this.dataDisposable.dispose(); // allow the data to be read by other handlers before disposing + }, 0); }) ); } diff --git a/packages/dockview-core/src/dnd/dropTargetAnchorContainer.scss b/packages/dockview-core/src/dnd/dropTargetAnchorContainer.scss new file mode 100644 index 000000000..0fd3dc5a5 --- /dev/null +++ b/packages/dockview-core/src/dnd/dropTargetAnchorContainer.scss @@ -0,0 +1,23 @@ +.dv-drop-target-container { + position: absolute; + z-index: 9999; + top: 0px; + left: 0px; + height: 100%; + width: 100%; + pointer-events: none; + overflow: hidden; + --dv-transition-duration: 300ms; + + .dv-drop-target-anchor { + position: relative; + border: var(--dv-drag-over-border); + transition: opacity var(--dv-transition-duration) ease-in, + top var(--dv-transition-duration) ease-out, + left var(--dv-transition-duration) ease-out, + width var(--dv-transition-duration) ease-out, + height var(--dv-transition-duration) ease-out; + background-color: var(--dv-drag-over-background-color); + opacity: 1; + } +} diff --git a/packages/dockview-core/src/dnd/dropTargetAnchorContainer.ts b/packages/dockview-core/src/dnd/dropTargetAnchorContainer.ts new file mode 100644 index 000000000..e43989c9e --- /dev/null +++ b/packages/dockview-core/src/dnd/dropTargetAnchorContainer.ts @@ -0,0 +1,102 @@ +import { CompositeDisposable, Disposable } from '../lifecycle'; +import { DropTargetTargetModel } from './droptarget'; + +export class DropTargetAnchorContainer extends CompositeDisposable { + private _model: + | { root: HTMLElement; overlay: HTMLElement; changed: boolean } + | undefined; + + private _outline: HTMLElement | undefined; + + private _disabled = false; + + get disabled(): boolean { + return this._disabled; + } + + set disabled(value: boolean) { + if (this.disabled === value) { + return; + } + + this._disabled = value; + + if (value) { + this.model?.clear(); + } + } + + get model(): DropTargetTargetModel | undefined { + if (this.disabled) { + return undefined; + } + + return { + clear: () => { + if (this._model) { + this._model.root.parentElement?.removeChild( + this._model.root + ); + } + this._model = undefined; + }, + exists: () => { + return !!this._model; + }, + getElements: (event?: DragEvent, outline?: HTMLElement) => { + const changed = this._outline !== outline; + this._outline = outline; + + if (this._model) { + this._model.changed = changed; + return this._model; + } + + const container = this.createContainer(); + const anchor = this.createAnchor(); + + this._model = { root: container, overlay: anchor, changed }; + + container.appendChild(anchor); + this.element.appendChild(container); + + if (event?.target instanceof HTMLElement) { + const targetBox = event.target.getBoundingClientRect(); + const box = this.element.getBoundingClientRect(); + + anchor.style.left = `${targetBox.left - box.left}px`; + anchor.style.top = `${targetBox.top - box.top}px`; + } + + return this._model; + }, + }; + } + + constructor(readonly element: HTMLElement, options: { disabled: boolean }) { + super(); + + this._disabled = options.disabled; + + this.addDisposables( + Disposable.from(() => { + this.model?.clear(); + }) + ); + } + + private createContainer(): HTMLElement { + const el = document.createElement('div'); + el.className = 'dv-drop-target-container'; + + return el; + } + + private createAnchor(): HTMLElement { + const el = document.createElement('div'); + el.className = 'dv-drop-target-anchor'; + el.style.visibility = 'hidden'; + + return el; + } +} diff --git a/packages/dockview-core/src/dnd/droptarget.scss b/packages/dockview-core/src/dnd/droptarget.scss index f23f318f7..7f2c8cb8b 100644 --- a/packages/dockview-core/src/dnd/droptarget.scss +++ b/packages/dockview-core/src/dnd/droptarget.scss @@ -1,5 +1,6 @@ .dv-drop-target { position: relative; + --dv-transition-duration: 70ms; > .dv-drop-target-dropzone { position: absolute; @@ -15,10 +16,13 @@ box-sizing: border-box; height: 100%; width: 100%; + border: var(--dv-drag-over-border); background-color: var(--dv-drag-over-background-color); - transition: top 70ms ease-out, left 70ms ease-out, - width 70ms ease-out, height 70ms ease-out, - opacity 0.15s ease-out; + transition: top var(--dv-transition-duration) ease-out, + left var(--dv-transition-duration) ease-out, + width var(--dv-transition-duration) ease-out, + height var(--dv-transition-duration) ease-out, + opacity var(--dv-transition-duration) ease-out; will-change: transform; pointer-events: none; diff --git a/packages/dockview-core/src/dnd/droptarget.ts b/packages/dockview-core/src/dnd/droptarget.ts index 702fed867..c611f890b 100644 --- a/packages/dockview-core/src/dnd/droptarget.ts +++ b/packages/dockview-core/src/dnd/droptarget.ts @@ -93,10 +93,26 @@ const DEFAULT_SIZE: MeasuredValue = { const SMALL_WIDTH_BOUNDARY = 100; const SMALL_HEIGHT_BOUNDARY = 100; +export interface DropTargetTargetModel { + getElements( + event?: DragEvent, + outline?: HTMLElement + ): { + root: HTMLElement; + overlay: HTMLElement; + changed: boolean; + }; + exists(): boolean; + clear(): void; +} + export interface DroptargetOptions { canDisplayOverlay: CanDisplayOverlay; acceptedTargetZones: Position[]; overlayModel?: DroptargetOverlayModel; + getOverrideTarget?: () => DropTargetTargetModel | undefined; + className?: string; + getOverlayOutline?: () => HTMLElement | null; } export class Droptarget extends CompositeDisposable { @@ -116,6 +132,18 @@ export class Droptarget extends CompositeDisposable { private static USED_EVENT_ID = '__dockview_droptarget_event_is_used__'; + private static ACTUAL_TARGET: Droptarget | undefined; + + private _disabled: boolean; + + get disabled(): boolean { + return this._disabled; + } + + set disabled(value: boolean) { + this._disabled = value; + } + get state(): Position | undefined { return this._state; } @@ -126,21 +154,35 @@ export class Droptarget extends CompositeDisposable { ) { super(); + this._disabled = false; + // use a set to take advantage of #.has this._acceptedTargetZonesSet = new Set( this.options.acceptedTargetZones ); this.dnd = new DragAndDropObserver(this.element, { - onDragEnter: () => undefined, + onDragEnter: () => { + this.options.getOverrideTarget?.()?.getElements(); + }, onDragOver: (e) => { + Droptarget.ACTUAL_TARGET = this; + + const overrideTraget = this.options.getOverrideTarget?.(); + if (this._acceptedTargetZonesSet.size === 0) { + if (overrideTraget) { + return; + } this.removeDropTarget(); return; } - const width = this.element.clientWidth; - const height = this.element.clientHeight; + const target = + this.options.getOverlayOutline?.() ?? this.element; + + const width = target.offsetWidth; + const height = target.offsetHeight; if (width === 0 || height === 0) { return; // avoid div!0 @@ -149,8 +191,8 @@ export class Droptarget extends CompositeDisposable { const rect = ( e.currentTarget as HTMLElement ).getBoundingClientRect(); - const x = e.clientX - rect.left; - const y = e.clientY - rect.top; + const x = (e.clientX ?? 0) - rect.left; + const y = (e.clientY ?? 0) - rect.top; const quadrant = this.calculateQuadrant( this._acceptedTargetZonesSet, @@ -172,6 +214,9 @@ export class Droptarget extends CompositeDisposable { } if (!this.options.canDisplayOverlay(e, quadrant)) { + if (overrideTraget) { + return; + } this.removeDropTarget(); return; } @@ -194,7 +239,9 @@ export class Droptarget extends CompositeDisposable { this.markAsUsed(e); - if (!this.targetElement) { + if (overrideTraget) { + // + } else if (!this.targetElement) { this.targetElement = document.createElement('div'); this.targetElement.className = 'dv-drop-target-dropzone'; this.overlayElement = document.createElement('div'); @@ -202,8 +249,16 @@ export class Droptarget extends CompositeDisposable { this._state = 'center'; this.targetElement.appendChild(this.overlayElement); - this.element.classList.add('dv-drop-target'); - this.element.append(this.targetElement); + target.classList.add('dv-drop-target'); + target.append(this.targetElement); + + // this.overlayElement.style.opacity = '0'; + + // requestAnimationFrame(() => { + // if (this.overlayElement) { + // this.overlayElement.style.opacity = ''; + // } + // }); } this.toggleClasses(quadrant, width, height); @@ -211,10 +266,32 @@ export class Droptarget extends CompositeDisposable { this._state = quadrant; }, onDragLeave: () => { + const target = this.options.getOverrideTarget?.(); + + if (target) { + return; + } + this.removeDropTarget(); }, - onDragEnd: () => { + onDragEnd: (e) => { + const target = this.options.getOverrideTarget?.(); + + if (target && Droptarget.ACTUAL_TARGET === this) { + if (this._state) { + // only stop the propagation of the event if we are dealing with it + // which is only when the target has state + e.stopPropagation(); + this._onDrop.fire({ + position: this._state, + nativeEvent: e, + }); + } + } + this.removeDropTarget(); + + target?.clear(); }, onDrop: (e) => { e.preventDefault(); @@ -223,6 +300,8 @@ export class Droptarget extends CompositeDisposable { this.removeDropTarget(); + this.options.getOverrideTarget?.()?.clear(); + if (state) { // only stop the propagation of the event if we are dealing with it // which is only when the target has state @@ -268,7 +347,9 @@ export class Droptarget extends CompositeDisposable { width: number, height: number ): void { - if (!this.overlayElement) { + const target = this.options.getOverrideTarget?.(); + + if (!target && !this.overlayElement) { return; } @@ -300,6 +381,103 @@ export class Droptarget extends CompositeDisposable { } } + if (target) { + const outlineEl = + this.options.getOverlayOutline?.() ?? this.element; + const elBox = outlineEl.getBoundingClientRect(); + + const ta = target.getElements(undefined, outlineEl); + const el = ta.root; + const overlay = ta.overlay; + + const bigbox = el.getBoundingClientRect(); + + const rootTop = elBox.top - bigbox.top; + const rootLeft = elBox.left - bigbox.left; + + const box = { + top: rootTop, + left: rootLeft, + width: width, + height: height, + }; + + if (rightClass) { + box.left = rootLeft + width * (1 - size); + box.width = width * size; + } else if (leftClass) { + box.width = width * size; + } else if (topClass) { + box.height = height * size; + } else if (bottomClass) { + box.top = rootTop + height * (1 - size); + box.height = height * size; + } + + if (isSmallX && isLeft) { + box.width = 4; + } + if (isSmallX && isRight) { + box.left = rootLeft + width - 4; + box.width = 4; + } + + const topPx = `${Math.round(box.top)}px`; + const leftPx = `${Math.round(box.left)}px`; + const widthPx = `${Math.round(box.width)}px`; + const heightPx = `${Math.round(box.height)}px`; + + if ( + overlay.style.top === topPx && + overlay.style.left === leftPx && + overlay.style.width === widthPx && + overlay.style.height === heightPx + ) { + return; + } + + overlay.style.top = topPx; + overlay.style.left = leftPx; + overlay.style.width = widthPx; + overlay.style.height = heightPx; + overlay.style.visibility = 'visible'; + + overlay.className = `dv-drop-target-anchor${ + this.options.className ? ` ${this.options.className}` : '' + }`; + + toggleClass(overlay, 'dv-drop-target-left', isLeft); + toggleClass(overlay, 'dv-drop-target-right', isRight); + toggleClass(overlay, 'dv-drop-target-top', isTop); + toggleClass(overlay, 'dv-drop-target-bottom', isBottom); + toggleClass( + overlay, + 'dv-drop-target-center', + quadrant === 'center' + ); + + if (ta.changed) { + toggleClass( + overlay, + 'dv-drop-target-anchor-container-changed', + true + ); + setTimeout(() => { + toggleClass( + overlay, + 'dv-drop-target-anchor-container-changed', + false + ); + }, 10); + } + + return; + } + + if (!this.overlayElement) { + return; + } + const box = { top: '0px', left: '0px', width: '100%', height: '100%' }; /** @@ -396,10 +574,12 @@ export class Droptarget extends CompositeDisposable { private removeDropTarget(): void { if (this.targetElement) { this._state = undefined; - this.element.removeChild(this.targetElement); + this.targetElement.parentElement?.classList.remove( + 'dv-drop-target' + ); + this.targetElement.remove(); this.targetElement = undefined; this.overlayElement = undefined; - this.element.classList.remove('dv-drop-target'); } } } diff --git a/packages/dockview-core/src/dnd/ghost.ts b/packages/dockview-core/src/dnd/ghost.ts index 2ff9c569f..df976c7cf 100644 --- a/packages/dockview-core/src/dnd/ghost.ts +++ b/packages/dockview-core/src/dnd/ghost.ts @@ -2,13 +2,14 @@ import { addClasses, removeClasses } from '../dom'; export function addGhostImage( dataTransfer: DataTransfer, - ghostElement: HTMLElement + ghostElement: HTMLElement, + options?: { x?: number; y?: number } ): void { // class dockview provides to force ghost image to be drawn on a different layer and prevent weird rendering issues addClasses(ghostElement, 'dv-dragged'); document.body.appendChild(ghostElement); - dataTransfer.setDragImage(ghostElement, 0, 0); + dataTransfer.setDragImage(ghostElement, options?.x ?? 0, options?.y ?? 0); setTimeout(() => { removeClasses(ghostElement, 'dv-dragged'); diff --git a/packages/dockview-core/src/dnd/groupDragHandler.ts b/packages/dockview-core/src/dnd/groupDragHandler.ts index bdda2be3b..2e3c9d281 100644 --- a/packages/dockview-core/src/dnd/groupDragHandler.ts +++ b/packages/dockview-core/src/dnd/groupDragHandler.ts @@ -72,9 +72,11 @@ export class GroupDragHandler extends DragHandler { ghostElement.style.lineHeight = '20px'; ghostElement.style.borderRadius = '12px'; ghostElement.style.position = 'absolute'; + ghostElement.style.pointerEvents = 'none'; + ghostElement.style.top = '-9999px'; ghostElement.textContent = `Multiple Panels (${this.group.size})`; - addGhostImage(dataTransfer, ghostElement); + addGhostImage(dataTransfer, ghostElement, { y: -10, x: 30 }); } return { diff --git a/packages/dockview-core/src/dockview/components/panel/content.ts b/packages/dockview-core/src/dockview/components/panel/content.ts index 4f66b03d3..6c37035b0 100644 --- a/packages/dockview-core/src/dockview/components/panel/content.ts +++ b/packages/dockview-core/src/dockview/components/panel/content.ts @@ -55,7 +55,15 @@ export class ContentContainer this.addDisposables(this._onDidFocus, this._onDidBlur); + const target = group.dropTargetContainer; + this.dropTarget = new Droptarget(this.element, { + getOverlayOutline: () => { + return accessor.options.theme?.dndPanelOverlay === 'group' + ? this.element.parentElement + : null; + }, + className: 'dv-drop-target-content', acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'], canDisplayOverlay: (event, position) => { if ( @@ -76,26 +84,12 @@ export class ContentContainer } if (data && data.viewId === this.accessor.id) { - if (data.groupId === this.group.id) { - if (position === 'center') { - // don't allow to drop on self for center position - return false; - } - if (data.panelId === null) { - // don't allow group move to drop anywhere on self - return false; - } - } - - const groupHasOnePanelAndIsActiveDragElement = - this.group.panels.length === 1 && - data.groupId === this.group.id; - - return !groupHasOnePanelAndIsActiveDragElement; + return true; } return this.group.canDisplayOverlay(event, position, 'content'); }, + getOverrideTarget: target ? () => target.model : undefined, }); this.addDisposables(this.dropTarget); diff --git a/packages/dockview-core/src/dockview/components/popupService.ts b/packages/dockview-core/src/dockview/components/popupService.ts new file mode 100644 index 000000000..58f0b9853 --- /dev/null +++ b/packages/dockview-core/src/dockview/components/popupService.ts @@ -0,0 +1,82 @@ +import { addDisposableWindowListener } from '../../events'; +import { + CompositeDisposable, + Disposable, + MutableDisposable, +} from '../../lifecycle'; + +export class PopupService extends CompositeDisposable { + private readonly _element: HTMLElement; + private _active: HTMLElement | null = null; + private readonly _activeDisposable = new MutableDisposable(); + + constructor(private readonly root: HTMLElement) { + super(); + + this._element = document.createElement('div'); + this._element.className = 'dv-popover-anchor'; + this._element.style.position = 'relative'; + + this.root.prepend(this._element); + + this.addDisposables( + Disposable.from(() => { + this.close(); + }), + this._activeDisposable + ); + } + + openPopover( + element: HTMLElement, + position: { x: number; y: number } + ): void { + this.close(); + + const wrapper = document.createElement('div'); + wrapper.style.position = 'absolute'; + wrapper.style.zIndex = '99'; + wrapper.appendChild(element); + + const anchorBox = this._element.getBoundingClientRect(); + const offsetX = anchorBox.left; + const offsetY = anchorBox.top; + + wrapper.style.top = `${position.y - offsetY}px`; + wrapper.style.left = `${position.x - offsetX}px`; + + this._element.appendChild(wrapper); + + this._active = wrapper; + + this._activeDisposable.value = new CompositeDisposable( + addDisposableWindowListener(window, 'pointerdown', (event) => { + const target = event.target; + + if (!(target instanceof HTMLElement)) { + return; + } + + let el: HTMLElement | null = target; + + while (el && el !== wrapper) { + el = el?.parentElement ?? null; + } + + if (el) { + return; // clicked within popover + } + + this.close(); + }) + ); + } + + close(): void { + if (this._active) { + this._active.remove(); + this._activeDisposable.dispose(); + this._active = null; + } + } +} diff --git a/packages/dockview-core/src/dockview/components/tab/defaultTab.scss b/packages/dockview-core/src/dockview/components/tab/defaultTab.scss index 0fdf53d78..3d2865583 100644 --- a/packages/dockview-core/src/dockview/components/tab/defaultTab.scss +++ b/packages/dockview-core/src/dockview/components/tab/defaultTab.scss @@ -58,15 +58,13 @@ position: relative; height: 100%; display: flex; - min-width: 80px; align-items: center; - padding: 0px 8px; white-space: nowrap; text-overflow: ellipsis; .dv-default-tab-content { - padding: 0px 8px; flex-grow: 1; + margin-right: 4px; } .dv-default-tab-action { diff --git a/packages/dockview-core/src/dockview/components/tab/defaultTab.ts b/packages/dockview-core/src/dockview/components/tab/defaultTab.ts index 80c30be5d..205e4e562 100644 --- a/packages/dockview-core/src/dockview/components/tab/defaultTab.ts +++ b/packages/dockview-core/src/dockview/components/tab/defaultTab.ts @@ -29,12 +29,6 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer { this._element.appendChild(this._content); this._element.appendChild(this.action); - this.addDisposables( - addDisposableListener(this.action, 'pointerdown', (ev) => { - ev.preventDefault(); - }) - ); - this.render(); } diff --git a/packages/dockview-core/src/dockview/components/tab/tab.ts b/packages/dockview-core/src/dockview/components/tab/tab.ts index 1eb1174d8..c56bcaccb 100644 --- a/packages/dockview-core/src/dockview/components/tab/tab.ts +++ b/packages/dockview-core/src/dockview/components/tab/tab.ts @@ -16,6 +16,7 @@ import { } from '../../../dnd/droptarget'; import { DragHandler } from '../../../dnd/abstractDragHandler'; import { IDockviewPanel } from '../../dockviewPanel'; +import { addGhostImage } from '../../../dnd/ghost'; class TabDragHandler extends DragHandler { private readonly panelTransfer = @@ -49,8 +50,8 @@ export class Tab extends CompositeDisposable { private readonly dropTarget: Droptarget; private content: ITabRenderer | undefined = undefined; - private readonly _onChanged = new Emitter(); - readonly onChanged: Event = this._onChanged.event; + private readonly _onPointDown = new Emitter(); + readonly onPointerDown: Event = this._onPointDown.event; private readonly _onDropped = new Emitter(); readonly onDrop: Event = this._onDropped.event; @@ -86,7 +87,8 @@ export class Tab extends CompositeDisposable { ); this.dropTarget = new Droptarget(this._element, { - acceptedTargetZones: ['center'], + acceptedTargetZones: ['left', 'right'], + overlayModel: { activationSize: { value: 50, type: 'percentage' } }, canDisplayOverlay: (event, position) => { if (this.group.locked) { return false; @@ -95,15 +97,7 @@ export class Tab extends CompositeDisposable { const data = getPanelData(); if (data && this.accessor.id === data.viewId) { - if ( - data.panelId === null && - data.groupId === this.group.id - ) { - // don't allow group move to drop on self - return false; - } - - return this.panel.id !== data.panelId; + return true; } return this.group.model.canDisplayOverlay( @@ -112,24 +106,38 @@ export class Tab extends CompositeDisposable { 'tab' ); }, + getOverrideTarget: () => group.model.dropTargetContainer?.model, }); this.onWillShowOverlay = this.dropTarget.onWillShowOverlay; this.addDisposables( - this._onChanged, + this._onPointDown, this._onDropped, this._onDragStart, dragHandler.onDragStart((event) => { + if (event.dataTransfer) { + const style = getComputedStyle(this.element); + const newNode = this.element.cloneNode(true) as HTMLElement; + Array.from(style).forEach((key) => + newNode.style.setProperty( + key, + style.getPropertyValue(key), + style.getPropertyPriority(key) + ) + ); + newNode.style.position = 'absolute'; + + addGhostImage(event.dataTransfer, newNode, { + y: -10, + x: 30, + }); + } this._onDragStart.fire(event); }), dragHandler, addDisposableListener(this._element, 'pointerdown', (event) => { - if (event.defaultPrevented) { - return; - } - - this._onChanged.fire(event); + this._onPointDown.fire(event); }), this.dropTarget.onDrop((event) => { this._onDropped.fire(event); diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabOverflowControl.scss b/packages/dockview-core/src/dockview/components/titlebar/tabOverflowControl.scss new file mode 100644 index 000000000..2d87ce7ca --- /dev/null +++ b/packages/dockview-core/src/dockview/components/titlebar/tabOverflowControl.scss @@ -0,0 +1,19 @@ +.dv-tabs-overflow-dropdown-default { + height: 100%; + color: var(--dv-activegroup-hiddenpanel-tab-color); + + margin: var(--dv-tab-margin); + display: flex; + align-items: center; + flex-shrink: 0; + padding: 0.25rem 0.5rem; + cursor: pointer; + + > span { + padding-left: 0.25rem; + } + + > svg { + transform: rotate(90deg); + } +} diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabOverflowControl.ts b/packages/dockview-core/src/dockview/components/titlebar/tabOverflowControl.ts new file mode 100644 index 000000000..1e27c661e --- /dev/null +++ b/packages/dockview-core/src/dockview/components/titlebar/tabOverflowControl.ts @@ -0,0 +1,25 @@ +import { createChevronRightButton } from '../../../svg'; + +export type DropdownElement = { + element: HTMLElement; + update: (params: { tabs: number }) => void; + dispose?: () => void; +}; + +export function createDropdownElementHandle(): DropdownElement { + const el = document.createElement('div'); + el.className = 'dv-tabs-overflow-dropdown-default'; + + const text = document.createElement('span'); + text.textContent = ``; + const icon = createChevronRightButton(); + el.appendChild(icon); + el.appendChild(text); + + return { + element: el, + update: (params: { tabs: number }) => { + text.textContent = `${params.tabs}`; + }, + }; +} diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabs.scss b/packages/dockview-core/src/dockview/components/titlebar/tabs.scss new file mode 100644 index 000000000..df38c6741 --- /dev/null +++ b/packages/dockview-core/src/dockview/components/titlebar/tabs.scss @@ -0,0 +1,83 @@ +.dv-tabs-container { + display: flex; + height: 100%; + overflow: hidden; + scrollbar-width: thin; // firefox + + &.dv-horizontal { + .dv-tabs-container { + .dv-tab { + &:last-child { + margin-right: 0; + } + + &:not(:nth-last-child(1)) { + margin-left: 0; + } + + &:not(:first-child)::before { + content: ' '; + position: absolute; + top: 0; + left: 0; + z-index: 5; + pointer-events: none; + background-color: var(--dv-tab-divider-color); + width: 1px; + height: 100%; + } + } + } + } + + &::-webkit-scrollbar { + height: 3px; + } + + /* Track */ + &::-webkit-scrollbar-track { + background: transparent; + } + + /* Handle */ + &::-webkit-scrollbar-thumb { + background: var(--dv-tabs-container-scrollbar-color); + } +} + +.dv-tab { + -webkit-user-drag: element; + outline: none; + padding: 0.25rem 0.5rem; + cursor: pointer; + position: relative; + box-sizing: border-box; + font-size: var(--dv-tab-font-size); + margin: var(--dv-tab-margin); +} + +.dv-tabs-overflow-container { + flex-direction: column; + height: unset; + border: 1px solid var(--dv-tab-divider-color); + background-color: var(--dv-group-view-background-color); + + .dv-tab { + &:not(:last-child) { + border-bottom: 1px solid var(--dv-tab-divider-color); + } + } + + .dv-active-tab { + background-color: var( + --dv-activegroup-visiblepanel-tab-background-color + ); + color: var(--dv-activegroup-visiblepanel-tab-color); + } + .dv-inactive-tab { + background-color: var( + --dv-activegroup-hiddenpanel-tab-background-color + ); + color: var(--dv-activegroup-hiddenpanel-tab-color); + } +} diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabs.ts b/packages/dockview-core/src/dockview/components/titlebar/tabs.ts new file mode 100644 index 000000000..b8a6d5499 --- /dev/null +++ b/packages/dockview-core/src/dockview/components/titlebar/tabs.ts @@ -0,0 +1,297 @@ +import { getPanelData } from '../../../dnd/dataTransfer'; +import { + isChildEntirelyVisibleWithinParent, + OverflowObserver, +} from '../../../dom'; +import { addDisposableListener, Emitter, Event } from '../../../events'; +import { + CompositeDisposable, + Disposable, + IValueDisposable, + MutableDisposable, +} from '../../../lifecycle'; +import { Scrollbar } from '../../../scrollbar'; +import { DockviewComponent } from '../../dockviewComponent'; +import { DockviewGroupPanel } from '../../dockviewGroupPanel'; +import { WillShowOverlayLocationEvent } from '../../dockviewGroupPanelModel'; +import { DockviewPanel, IDockviewPanel } from '../../dockviewPanel'; +import { Tab } from '../tab/tab'; +import { TabDragEvent, TabDropIndexEvent } from './tabsContainer'; + +export class Tabs extends CompositeDisposable { + private readonly _element: HTMLElement; + private readonly _tabsList: HTMLElement; + private readonly _observerDisposable = new MutableDisposable(); + + private _tabs: IValueDisposable[] = []; + private selectedIndex = -1; + private _showTabsOverflowControl = false; + + private readonly _onTabDragStart = new Emitter(); + readonly onTabDragStart: Event = this._onTabDragStart.event; + + private readonly _onDrop = new Emitter(); + readonly onDrop: Event = this._onDrop.event; + + private readonly _onWillShowOverlay = + new Emitter(); + readonly onWillShowOverlay: Event = + this._onWillShowOverlay.event; + + private readonly _onOverflowTabsChange = new Emitter<{ + tabs: string[]; + reset: boolean; + }>(); + readonly onOverflowTabsChange = this._onOverflowTabsChange.event; + + get showTabsOverflowControl(): boolean { + return this._showTabsOverflowControl; + } + + set showTabsOverflowControl(value: boolean) { + if (this._showTabsOverflowControl == value) { + return; + } + + this._showTabsOverflowControl = value; + + if (value) { + const observer = new OverflowObserver(this._tabsList); + + this._observerDisposable.value = new CompositeDisposable( + observer, + observer.onDidChange((event) => { + const hasOverflow = event.hasScrollX || event.hasScrollY; + this.toggleDropdown({ reset: !hasOverflow }); + }), + addDisposableListener(this._tabsList, 'scroll', () => { + this.toggleDropdown({ reset: false }); + }) + ); + } + } + + get element(): HTMLElement { + return this._element; + } + + get panels(): string[] { + return this._tabs.map((_) => _.value.panel.id); + } + + get size(): number { + return this._tabs.length; + } + + get tabs(): Tab[] { + return this._tabs.map((_) => _.value); + } + + constructor( + private readonly group: DockviewGroupPanel, + private readonly accessor: DockviewComponent, + options: { + showTabsOverflowControl: boolean; + } + ) { + super(); + + this._tabsList = document.createElement('div'); + this._tabsList.className = 'dv-tabs-container dv-horizontal'; + + this.showTabsOverflowControl = options.showTabsOverflowControl; + + const scrollbar = new Scrollbar(this._tabsList); + this._element = scrollbar.element; + + this.addDisposables( + this._onOverflowTabsChange, + this._observerDisposable, + scrollbar, + this._onWillShowOverlay, + this._onDrop, + this._onTabDragStart, + addDisposableListener(this.element, 'pointerdown', (event) => { + if (event.defaultPrevented) { + return; + } + + const isLeftClick = event.button === 0; + + if (isLeftClick) { + this.accessor.doSetGroupActive(this.group); + } + }), + Disposable.from(() => { + for (const { value, disposable } of this._tabs) { + disposable.dispose(); + value.dispose(); + } + + this._tabs = []; + }) + ); + } + + indexOf(id: string): number { + return this._tabs.findIndex((tab) => tab.value.panel.id === id); + } + + isActive(tab: Tab): boolean { + return ( + this.selectedIndex > -1 && + this._tabs[this.selectedIndex].value === tab + ); + } + + setActivePanel(panel: IDockviewPanel): void { + let runningWidth = 0; + + for (const tab of this._tabs) { + const isActivePanel = panel.id === tab.value.panel.id; + tab.value.setActive(isActivePanel); + + if (isActivePanel) { + const element = tab.value.element; + const parentElement = element.parentElement!; + + if ( + runningWidth < parentElement.scrollLeft || + runningWidth + element.clientWidth > + parentElement.scrollLeft + parentElement.clientWidth + ) { + parentElement.scrollLeft = runningWidth; + } + } + + runningWidth += tab.value.element.clientWidth; + } + } + + openPanel(panel: IDockviewPanel, index: number = this._tabs.length): void { + if (this._tabs.find((tab) => tab.value.panel.id === panel.id)) { + return; + } + const tab = new Tab(panel, this.accessor, this.group); + tab.setContent(panel.view.tab); + + const disposable = new CompositeDisposable( + tab.onDragStart((event) => { + this._onTabDragStart.fire({ nativeEvent: event, panel }); + }), + tab.onPointerDown((event) => { + if (event.defaultPrevented) { + return; + } + + const isFloatingGroupsEnabled = + !this.accessor.options.disableFloatingGroups; + + const isFloatingWithOnePanel = + this.group.api.location.type === 'floating' && + this.size === 1; + + if ( + isFloatingGroupsEnabled && + !isFloatingWithOnePanel && + event.shiftKey + ) { + event.preventDefault(); + + const panel = this.accessor.getGroupPanel(tab.panel.id); + + const { top, left } = tab.element.getBoundingClientRect(); + const { top: rootTop, left: rootLeft } = + this.accessor.element.getBoundingClientRect(); + + this.accessor.addFloatingGroup(panel as DockviewPanel, { + x: left - rootLeft, + y: top - rootTop, + inDragMode: true, + }); + return; + } + + switch (event.button) { + case 0: // left click or touch + if (this.group.activePanel !== panel) { + this.group.model.openPanel(panel); + } + break; + } + }), + tab.onDrop((event) => { + this._onDrop.fire({ + event: event.nativeEvent, + index: this._tabs.findIndex((x) => x.value === tab), + }); + }), + tab.onWillShowOverlay((event) => { + this._onWillShowOverlay.fire( + new WillShowOverlayLocationEvent(event, { + kind: 'tab', + panel: this.group.activePanel, + api: this.accessor.api, + group: this.group, + getData: getPanelData, + }) + ); + }) + ); + + const value: IValueDisposable = { value: tab, disposable }; + + this.addTab(value, index); + } + + delete(id: string): void { + const index = this.indexOf(id); + const tabToRemove = this._tabs.splice(index, 1)[0]; + + const { value, disposable } = tabToRemove; + + disposable.dispose(); + value.dispose(); + value.element.remove(); + } + + private addTab( + tab: IValueDisposable, + index: number = this._tabs.length + ): void { + if (index < 0 || index > this._tabs.length) { + throw new Error('invalid location'); + } + + this._tabsList.insertBefore( + tab.value.element, + this._tabsList.children[index] + ); + + this._tabs = [ + ...this._tabs.slice(0, index), + tab, + ...this._tabs.slice(index), + ]; + + if (this.selectedIndex < 0) { + this.selectedIndex = index; + } + } + + private toggleDropdown(options: { reset: boolean }): void { + const tabs = options.reset + ? [] + : this._tabs + .filter( + (tab) => + !isChildEntirelyVisibleWithinParent( + tab.value.element, + this._tabsList + ) + ) + .map((x) => x.value.panel.id); + + this._onOverflowTabsChange.fire({ tabs, reset: options.reset }); + } +} diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.scss b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.scss index fef520e03..1ed918a1c 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.scss +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.scss @@ -7,17 +7,17 @@ font-size: var(--dv-tabs-and-actions-container-font-size); &.dv-single-tab.dv-full-width-single-tab { - .dv-tabs-container { - flex-grow: 1; - - .dv-tab { + .dv-tabs-container { flex-grow: 1; - } - } - .dv-void-container { - flex-grow: 0; - } + .dv-tab { + flex-grow: 1; + } + } + + .dv-void-container { + flex-grow: 0; + } } .dv-void-container { @@ -26,46 +26,7 @@ cursor: grab; } - .dv-tabs-container { + .dv-right-actions-container { display: flex; - overflow-x: overlay; - overflow-y: hidden; - - scrollbar-width: thin; // firefox - - &::-webkit-scrollbar { - height: 3px; - } - - /* Track */ - &::-webkit-scrollbar-track { - background: transparent; - } - - /* Handle */ - &::-webkit-scrollbar-thumb { - background: var(--dv-tabs-container-scrollbar-color); - } - - .dv-tab { - -webkit-user-drag: element; - outline: none; - min-width: 75px; - cursor: pointer; - position: relative; - box-sizing: border-box; - - &:not(:first-child)::before { - content: ' '; - position: absolute; - top: 0; - left: 0; - z-index: 5; - pointer-events: none; - background-color: var(--dv-tab-divider-color); - width: 1px; - height: 100%; - } - } } } diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts index c30d1175e..e02533611 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts @@ -1,17 +1,23 @@ import { IDisposable, CompositeDisposable, - IValueDisposable, + Disposable, + MutableDisposable, } from '../../../lifecycle'; import { addDisposableListener, Emitter, Event } from '../../../events'; import { Tab } from '../tab/tab'; import { DockviewGroupPanel } from '../../dockviewGroupPanel'; import { VoidContainer } from './voidContainer'; import { toggleClass } from '../../../dom'; -import { DockviewPanel, IDockviewPanel } from '../../dockviewPanel'; +import { IDockviewPanel } from '../../dockviewPanel'; import { DockviewComponent } from '../../dockviewComponent'; import { WillShowOverlayLocationEvent } from '../../dockviewGroupPanelModel'; import { getPanelData } from '../../../dnd/dataTransfer'; +import { Tabs } from './tabs'; +import { + createDropdownElementHandle, + DropdownElement, +} from './tabOverflowControl'; export interface TabDropIndexEvent { readonly event: DragEvent; @@ -56,25 +62,28 @@ export class TabsContainer implements ITabsContainer { private readonly _element: HTMLElement; - private readonly tabContainer: HTMLElement; + private readonly tabs: Tabs; private readonly rightActionsContainer: HTMLElement; private readonly leftActionsContainer: HTMLElement; private readonly preActionsContainer: HTMLElement; private readonly voidContainer: VoidContainer; - private tabs: IValueDisposable[] = []; - private selectedIndex = -1; private rightActions: HTMLElement | undefined; private leftActions: HTMLElement | undefined; private preActions: HTMLElement | undefined; private _hidden = false; + private dropdownPart: DropdownElement | null = null; + private _overflowTabs: string[] = []; + private readonly _dropdownDisposable = new MutableDisposable(); + private readonly _onDrop = new Emitter(); readonly onDrop: Event = this._onDrop.event; - private readonly _onTabDragStart = new Emitter(); - readonly onTabDragStart: Event = this._onTabDragStart.event; + get onTabDragStart(): Event { + return this.tabs.onTabDragStart; + } private readonly _onGroupDragStart = new Emitter(); readonly onGroupDragStart: Event = @@ -86,11 +95,11 @@ export class TabsContainer this._onWillShowOverlay.event; get panels(): string[] { - return this.tabs.map((_) => _.value.panel.id); + return this.tabs.panels; } get size(): number { - return this.tabs.length; + return this.tabs.size; } get hidden(): boolean { @@ -102,6 +111,116 @@ export class TabsContainer this.element.style.display = value ? 'none' : ''; } + get element(): HTMLElement { + return this._element; + } + + constructor( + private readonly accessor: DockviewComponent, + private readonly group: DockviewGroupPanel + ) { + super(); + + this._element = document.createElement('div'); + this._element.className = 'dv-tabs-and-actions-container'; + + toggleClass( + this._element, + 'dv-full-width-single-tab', + this.accessor.options.singleTabMode === 'fullwidth' + ); + + this.rightActionsContainer = document.createElement('div'); + this.rightActionsContainer.className = 'dv-right-actions-container'; + + this.leftActionsContainer = document.createElement('div'); + this.leftActionsContainer.className = 'dv-left-actions-container'; + + this.preActionsContainer = document.createElement('div'); + this.preActionsContainer.className = 'dv-pre-actions-container'; + + this.tabs = new Tabs(group, accessor, { + showTabsOverflowControl: !accessor.options.disableTabsOverflowList, + }); + + this.voidContainer = new VoidContainer(this.accessor, this.group); + + this._element.appendChild(this.preActionsContainer); + this._element.appendChild(this.tabs.element); + this._element.appendChild(this.leftActionsContainer); + this._element.appendChild(this.voidContainer.element); + this._element.appendChild(this.rightActionsContainer); + + this.addDisposables( + accessor.onDidOptionsChange(() => { + this.tabs.showTabsOverflowControl = + !accessor.options.disableTabsOverflowList; + }), + this.tabs.onOverflowTabsChange((event) => { + this.toggleDropdown(event); + }), + this.tabs, + this._onWillShowOverlay, + this._onDrop, + this._onGroupDragStart, + this.voidContainer, + this.voidContainer.onDragStart((event) => { + this._onGroupDragStart.fire({ + nativeEvent: event, + group: this.group, + }); + }), + this.voidContainer.onDrop((event) => { + this._onDrop.fire({ + event: event.nativeEvent, + index: this.tabs.size, + }); + }), + this.voidContainer.onWillShowOverlay((event) => { + this._onWillShowOverlay.fire( + new WillShowOverlayLocationEvent(event, { + kind: 'header_space', + panel: this.group.activePanel, + api: this.accessor.api, + group: this.group, + getData: getPanelData, + }) + ); + }), + addDisposableListener( + this.voidContainer.element, + 'pointerdown', + (event) => { + if (event.defaultPrevented) { + return; + } + + const isFloatingGroupsEnabled = + !this.accessor.options.disableFloatingGroups; + + if ( + isFloatingGroupsEnabled && + event.shiftKey && + this.group.api.location.type !== 'floating' + ) { + event.preventDefault(); + + const { top, left } = + this.element.getBoundingClientRect(); + const { top: rootTop, left: rootLeft } = + this.accessor.element.getBoundingClientRect(); + + this.accessor.addFloatingGroup(this.group, { + x: left - rootLeft + 20, + y: top - rootTop + 20, + inDragMode: true, + }); + } + } + ) + ); + } + show(): void { if (!this.hidden) { this.element.style.display = ''; @@ -154,280 +273,124 @@ export class TabsContainer } } - get element(): HTMLElement { - return this._element; + isActive(tab: Tab): boolean { + return this.tabs.isActive(tab); } - public isActive(tab: Tab): boolean { - return ( - this.selectedIndex > -1 && - this.tabs[this.selectedIndex].value === tab - ); + indexOf(id: string): number { + return this.tabs.indexOf(id); } - public indexOf(id: string): number { - return this.tabs.findIndex((tab) => tab.value.panel.id === id); - } - - constructor( - private readonly accessor: DockviewComponent, - private readonly group: DockviewGroupPanel - ) { - super(); - - this._element = document.createElement('div'); - this._element.className = 'dv-tabs-and-actions-container'; - - toggleClass( - this._element, - 'dv-full-width-single-tab', - this.accessor.options.singleTabMode === 'fullwidth' - ); - - this.rightActionsContainer = document.createElement('div'); - this.rightActionsContainer.className = 'dv-right-actions-container'; - - this.leftActionsContainer = document.createElement('div'); - this.leftActionsContainer.className = 'dv-left-actions-container'; - - this.preActionsContainer = document.createElement('div'); - this.preActionsContainer.className = 'dv-pre-actions-container'; - - this.tabContainer = document.createElement('div'); - this.tabContainer.className = 'dv-tabs-container'; - - this.voidContainer = new VoidContainer(this.accessor, this.group); - - this._element.appendChild(this.preActionsContainer); - this._element.appendChild(this.tabContainer); - this._element.appendChild(this.leftActionsContainer); - this._element.appendChild(this.voidContainer.element); - this._element.appendChild(this.rightActionsContainer); - - this.addDisposables( - this.accessor.onDidAddPanel((e) => { - if (e.api.group === this.group) { - toggleClass( - this._element, - 'dv-single-tab', - this.size === 1 - ); - } - }), - this.accessor.onDidRemovePanel((e) => { - if (e.api.group === this.group) { - toggleClass( - this._element, - 'dv-single-tab', - this.size === 1 - ); - } - }), - this._onWillShowOverlay, - this._onDrop, - this._onTabDragStart, - this._onGroupDragStart, - this.voidContainer, - this.voidContainer.onDragStart((event) => { - this._onGroupDragStart.fire({ - nativeEvent: event, - group: this.group, - }); - }), - this.voidContainer.onDrop((event) => { - this._onDrop.fire({ - event: event.nativeEvent, - index: this.tabs.length, - }); - }), - this.voidContainer.onWillShowOverlay((event) => { - this._onWillShowOverlay.fire( - new WillShowOverlayLocationEvent(event, { - kind: 'header_space', - panel: this.group.activePanel, - api: this.accessor.api, - group: this.group, - getData: getPanelData, - }) - ); - }), - addDisposableListener( - this.voidContainer.element, - 'pointerdown', - (event) => { - const isFloatingGroupsEnabled = - !this.accessor.options.disableFloatingGroups; - - if ( - isFloatingGroupsEnabled && - event.shiftKey && - this.group.api.location.type !== 'floating' - ) { - event.preventDefault(); - - const { top, left } = - this.element.getBoundingClientRect(); - const { top: rootTop, left: rootLeft } = - this.accessor.element.getBoundingClientRect(); - - this.accessor.addFloatingGroup(this.group, { - x: left - rootLeft + 20, - y: top - rootTop + 20, - inDragMode: true, - }); - } - } - ), - addDisposableListener(this.tabContainer, 'pointerdown', (event) => { - if (event.defaultPrevented) { - return; - } - - const isLeftClick = event.button === 0; - - if (isLeftClick) { - this.accessor.doSetGroupActive(this.group); - } - }) - ); - } - - public setActive(_isGroupActive: boolean) { + setActive(_isGroupActive: boolean) { // noop } - private addTab( - tab: IValueDisposable, - index: number = this.tabs.length - ): void { - if (index < 0 || index > this.tabs.length) { - throw new Error('invalid location'); - } - - this.tabContainer.insertBefore( - tab.value.element, - this.tabContainer.children[index] - ); - - this.tabs = [ - ...this.tabs.slice(0, index), - tab, - ...this.tabs.slice(index), - ]; - - if (this.selectedIndex < 0) { - this.selectedIndex = index; - } + delete(id: string): void { + this.tabs.delete(id); + this.updateClassnames(); } - public delete(id: string): void { - const index = this.tabs.findIndex((tab) => tab.value.panel.id === id); - - const tabToRemove = this.tabs.splice(index, 1)[0]; - - const { value, disposable } = tabToRemove; - - disposable.dispose(); - value.dispose(); - value.element.remove(); + setActivePanel(panel: IDockviewPanel): void { + this.tabs.setActivePanel(panel); } - public setActivePanel(panel: IDockviewPanel): void { - this.tabs.forEach((tab) => { - const isActivePanel = panel.id === tab.value.panel.id; - tab.value.setActive(isActivePanel); - }); + openPanel(panel: IDockviewPanel, index: number = this.tabs.size): void { + this.tabs.openPanel(panel, index); + this.updateClassnames(); } - public openPanel( - panel: IDockviewPanel, - index: number = this.tabs.length - ): void { - if (this.tabs.find((tab) => tab.value.panel.id === panel.id)) { - return; - } - const tab = new Tab(panel, this.accessor, this.group); - tab.setContent(panel.view.tab); - - const disposable = new CompositeDisposable( - tab.onDragStart((event) => { - this._onTabDragStart.fire({ nativeEvent: event, panel }); - }), - tab.onChanged((event) => { - const isFloatingGroupsEnabled = - !this.accessor.options.disableFloatingGroups; - - const isFloatingWithOnePanel = - this.group.api.location.type === 'floating' && - this.size === 1; - - if ( - isFloatingGroupsEnabled && - !isFloatingWithOnePanel && - event.shiftKey - ) { - event.preventDefault(); - - const panel = this.accessor.getGroupPanel(tab.panel.id); - - const { top, left } = tab.element.getBoundingClientRect(); - const { top: rootTop, left: rootLeft } = - this.accessor.element.getBoundingClientRect(); - - this.accessor.addFloatingGroup(panel as DockviewPanel, { - x: left - rootLeft, - y: top - rootTop, - inDragMode: true, - }); - return; - } - - const isLeftClick = event.button === 0; - - if (!isLeftClick || event.defaultPrevented) { - return; - } - - if (this.group.activePanel !== panel) { - this.group.model.openPanel(panel); - } - }), - tab.onDrop((event) => { - this._onDrop.fire({ - event: event.nativeEvent, - index: this.tabs.findIndex((x) => x.value === tab), - }); - }), - tab.onWillShowOverlay((event) => { - this._onWillShowOverlay.fire( - new WillShowOverlayLocationEvent(event, { - kind: 'tab', - panel: this.group.activePanel, - api: this.accessor.api, - group: this.group, - getData: getPanelData, - }) - ); - }) - ); - - const value: IValueDisposable = { value: tab, disposable }; - - this.addTab(value, index); - } - - public closePanel(panel: IDockviewPanel): void { + closePanel(panel: IDockviewPanel): void { this.delete(panel.id); } - public dispose(): void { - super.dispose(); + private updateClassnames(): void { + toggleClass(this._element, 'dv-single-tab', this.size === 1); + } - for (const { value, disposable } of this.tabs) { - disposable.dispose(); - value.dispose(); + private toggleDropdown(options: { tabs: string[]; reset: boolean }): void { + const tabs = options.reset ? [] : options.tabs; + this._overflowTabs = tabs; + + if (this._overflowTabs.length > 0 && this.dropdownPart) { + this.dropdownPart.update({ tabs: tabs.length }); + return; } - this.tabs = []; + if (this._overflowTabs.length === 0) { + this._dropdownDisposable.dispose(); + return; + } + + const root = document.createElement('div'); + root.className = 'dv-tabs-overflow-dropdown-root'; + + const part = createDropdownElementHandle(); + part.update({ tabs: tabs.length }); + + this.dropdownPart = part; + + root.appendChild(part.element); + this.rightActionsContainer.prepend(root); + + this._dropdownDisposable.value = new CompositeDisposable( + Disposable.from(() => { + root.remove(); + this.dropdownPart?.dispose?.(); + this.dropdownPart = null; + }), + addDisposableListener( + root, + 'pointerdown', + (event) => { + event.preventDefault(); + }, + { capture: true } + ), + addDisposableListener(root, 'click', (event) => { + const el = document.createElement('div'); + el.style.overflow = 'auto'; + el.className = 'dv-tabs-overflow-container'; + + for (const tab of this.tabs.tabs.filter((tab) => + this._overflowTabs.includes(tab.panel.id) + )) { + const panelObject = this.group.panels.find( + (panel) => panel === tab.panel + )!; + + const tabComponent = + panelObject.view.createTabRenderer('headerOverflow'); + + const child = tabComponent.element; + + const wrapper = document.createElement('div'); + toggleClass(wrapper, 'dv-tab', true); + toggleClass( + wrapper, + 'dv-active-tab', + panelObject.api.isActive + ); + toggleClass( + wrapper, + 'dv-inactive-tab', + !panelObject.api.isActive + ); + + wrapper.addEventListener('mousedown', () => { + this.accessor.popupService.close(); + tab.element.scrollIntoView(); + tab.panel.api.setActive(); + }); + wrapper.appendChild(child); + + el.appendChild(wrapper); + } + + this.accessor.popupService.openPopover(el, { + x: event.clientX, + y: event.clientY, + }); + }) + ); } } diff --git a/packages/dockview-core/src/dockview/components/titlebar/voidContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/voidContainer.ts index 6e9ea0c47..29e31b9b6 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/voidContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/voidContainer.ts @@ -1,4 +1,3 @@ -import { last } from '../../../array'; import { getPanelData } from '../../../dnd/dataTransfer'; import { Droptarget, @@ -10,6 +9,7 @@ import { DockviewComponent } from '../../dockviewComponent'; import { addDisposableListener, Emitter, Event } from '../../../events'; import { CompositeDisposable } from '../../../lifecycle'; import { DockviewGroupPanel } from '../../dockviewGroupPanel'; +import { DockviewGroupPanelModel } from '../../dockviewGroupPanelModel'; export class VoidContainer extends CompositeDisposable { private readonly _element: HTMLElement; @@ -54,16 +54,7 @@ export class VoidContainer extends CompositeDisposable { const data = getPanelData(); if (data && this.accessor.id === data.viewId) { - if ( - data.panelId === null && - data.groupId === this.group.id - ) { - // don't allow group move to drop on self - return false; - } - - // don't show the overlay if the tab being dragged is the last panel of this group - return last(this.group.panels)?.id !== data.panelId; + return true; } return group.model.canDisplayOverlay( @@ -72,6 +63,7 @@ export class VoidContainer extends CompositeDisposable { 'header_space' ); }, + getOverrideTarget: () => group.model.dropTargetContainer?.model, }); this.onWillShowOverlay = this.dropTraget.onWillShowOverlay; diff --git a/packages/dockview-core/src/dockview/dockviewComponent.scss b/packages/dockview-core/src/dockview/dockviewComponent.scss index 386bf2a82..3340e976a 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.scss +++ b/packages/dockview-core/src/dockview/dockviewComponent.scss @@ -18,7 +18,10 @@ .dv-groupview { &.dv-active-group { - > .dv-tabs-and-actions-container > .dv-tabs-container > .dv-tab { + > .dv-tabs-and-actions-container + > .dv-scrollable + > .dv-tabs-container + > .dv-tab { &.dv-active-tab { background-color: var( --dv-activegroup-visiblepanel-tab-background-color @@ -34,7 +37,10 @@ } } &.dv-inactive-group { - > .dv-tabs-and-actions-container > .dv-tabs-container > .dv-tab { + > .dv-tabs-and-actions-container + > .dv-scrollable + > .dv-tabs-container + > .dv-tab { &.dv-active-tab { background-color: var( --dv-inactivegroup-visiblepanel-tab-background-color diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index c7224d687..13bcb0ec2 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -54,6 +54,7 @@ import { Parameters } from '../panel/types'; import { Overlay } from '../overlay/overlay'; import { addTestId, + Classnames, getDockviewTheme, toggleClass, watchElementResize, @@ -73,6 +74,10 @@ import { OverlayRenderContainer, } from '../overlay/overlayRenderContainer'; import { PopoutWindow } from '../popoutWindow'; +import { StrictEventsSequencing } from './strictEventsSequencing'; +import { PopupService } from './components/popupService'; +import { DropTargetAnchorContainer } from '../dnd/dropTargetAnchorContainer'; +import { themeAbyss } from './theme'; const DEFAULT_ROOT_OVERLAY_MODEL: DroptargetOverlayModel = { activationSize: { type: 'pixels', value: 10 }, @@ -190,7 +195,6 @@ export interface IDockviewComponent extends IBaseGrid { readonly totalPanels: number; readonly panels: IDockviewPanel[]; readonly orientation: Orientation; - readonly gap: number; readonly onDidDrop: Event; readonly onWillDrop: Event; readonly onWillShowOverlay: Event; @@ -252,9 +256,12 @@ export class DockviewComponent private readonly _deserializer = new DefaultDockviewDeserialzier(this); private readonly _api: DockviewApi; private _options: Exclude; - private watermark: IWatermarkRenderer | null = null; + private _watermark: IWatermarkRenderer | null = null; + private readonly _themeClassnames: Classnames; readonly overlayRenderContainer: OverlayRenderContainer; + readonly popupService: PopupService; + readonly rootDropTargetContainer: DropTargetAnchorContainer; private readonly _onWillDragPanel = new Emitter(); readonly onWillDragPanel: Event = this._onWillDragPanel.event; @@ -319,6 +326,9 @@ export class DockviewComponent readonly onDidAddGroup: Event = this._onDidAddGroup.event; + private readonly _onDidOptionsChange = new Emitter(); + readonly onDidOptionsChange: Event = this._onDidOptionsChange.event; + private readonly _onDidActiveGroupChange = new Emitter< DockviewGroupPanel | undefined >(); @@ -359,16 +369,12 @@ export class DockviewComponent return this._api; } - get gap(): number { - return this.gridview.margin; - } - get floatingGroups(): DockviewFloatingGroupPanel[] { return this._floatingGroups; } - constructor(parentElement: HTMLElement, options: DockviewComponentOptions) { - super(parentElement, { + constructor(container: HTMLElement, options: DockviewComponentOptions) { + super(container, { proportionalLayout: true, orientation: Orientation.HORIZONTAL, styles: options.hideBorders @@ -376,10 +382,20 @@ export class DockviewComponent : undefined, disableAutoResizing: options.disableAutoResizing, locked: options.locked, - margin: options.gap, + margin: options.theme?.gap ?? 0, className: options.className, }); + this.popupService = new PopupService(this.element); + + this.updateDropTargetModel(options); + + this._themeClassnames = new Classnames(this.element); + + this.rootDropTargetContainer = new DropTargetAnchorContainer( + this.element, + { disabled: true } + ); this.overlayRenderContainer = new OverlayRenderContainer( this.gridview.element, this @@ -388,7 +404,12 @@ export class DockviewComponent toggleClass(this.gridview.element, 'dv-dockview', true); toggleClass(this.element, 'dv-debug', !!options.debug); + if (options.debug) { + this.addDisposables(new StrictEventsSequencing(this)); + } + this.addDisposables( + this.rootDropTargetContainer, this.overlayRenderContainer, this._onWillDragPanel, this._onWillDragGroup, @@ -404,6 +425,8 @@ export class DockviewComponent this._onDidRemoveGroup, this._onDidActiveGroupChange, this._onUnhandledDragOverEvent, + this._onDidMaximizedGroupChange, + this._onDidOptionsChange, this.onDidViewVisibilityChangeMicroTaskQueue(() => { this.updateWatermark(); }), @@ -458,8 +481,10 @@ export class DockviewComponent ); this._options = options; + this.updateTheme(); this._rootDropTarget = new Droptarget(this.element, { + className: 'dv-drop-target-edge', canDisplayOverlay: (event, position) => { const data = getPanelData(); @@ -500,6 +525,7 @@ export class DockviewComponent acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'], overlayModel: this.options.rootOverlayModel ?? DEFAULT_ROOT_OVERLAY_MODEL, + getOverrideTarget: () => this.rootDropTargetContainer?.model, }); this.addDisposables( @@ -706,6 +732,8 @@ export class DockviewComponent _window.window!.innerHeight ); + let floatingBox: AnchoredBox | undefined; + if (!options?.overridePopoutGroup && isGroupAddedToDom) { if (itemToPopout instanceof DockviewPanel) { this.movingLock(() => { @@ -727,7 +755,16 @@ export class DockviewComponent break; case 'floating': case 'popout': + floatingBox = this._floatingGroups + .find( + (value) => + value.group.api.id === + itemToPopout.api.id + ) + ?.overlay.toJSON(); + this.removeGroup(referenceGroup); + break; } } @@ -739,6 +776,15 @@ export class DockviewComponent popoutContainer.appendChild(group.element); + const anchor = document.createElement('div'); + const dropTargetContainer = new DropTargetAnchorContainer( + anchor, + { disabled: this.rootDropTargetContainer.disabled } + ); + popoutContainer.appendChild(anchor); + + group.model.dropTargetContainer = dropTargetContainer; + group.model.location = { type: 'popout', getWindow: () => _window.window!, @@ -804,6 +850,10 @@ export class DockviewComponent ), overlayRenderContainer, Disposable.from(() => { + if (this.isDisposed) { + return; // cleanup may run after instance is disposed + } + if ( isGroupAddedToDom && this.getPanel(referenceGroup.id) @@ -825,21 +875,47 @@ export class DockviewComponent }); } } else if (this.getPanel(group.id)) { - this.doRemoveGroup(group, { - skipDispose: true, - skipActive: true, - skipPopoutReturn: true, - }); - - const removedGroup = group; - - removedGroup.model.renderContainer = + group.model.renderContainer = this.overlayRenderContainer; - removedGroup.model.location = { type: 'grid' }; - returnedGroup = removedGroup; + group.model.dropTargetContainer = + this.rootDropTargetContainer; + returnedGroup = group; - this.doAddGroup(removedGroup, [0]); - this.doSetGroupAndPanelActive(removedGroup); + const alreadyRemoved = !this._popoutGroups.find( + (p) => p.popoutGroup === group + ); + + if (alreadyRemoved) { + /** + * If this popout group was explicitly removed then we shouldn't run the additional + * steps. To tell if the running of this disposable is the result of this popout group + * being explicitly removed we can check if this popout group is still referenced in + * the `this._popoutGroups` list. + */ + return; + } + + if (floatingBox) { + this.addFloatingGroup(group, { + height: floatingBox.height, + width: floatingBox.width, + position: floatingBox, + }); + } else { + this.doRemoveGroup(group, { + skipDispose: true, + skipActive: true, + skipPopoutReturn: true, + }); + + group.model.location = { type: 'grid' }; + + this.movingLock(() => { + // suppress group add events since the group already exists + this.doAddGroup(group, [0]); + }); + } + this.doSetGroupAndPanelActive(group); } }) ); @@ -1117,18 +1193,14 @@ export class DockviewComponent } } - if ('rootOverlayModel' in options) { - this._rootDropTarget.setOverlayModel( - options.rootOverlayModel ?? DEFAULT_ROOT_OVERLAY_MODEL - ); - } - - if ('gap' in options) { - this.gridview.margin = options.gap ?? 0; - } + this.updateDropTargetModel(options); this._options = { ...this.options, ...options }; + if ('theme' in options) { + this.updateTheme(); + } + this.layout(this.gridview.width, this.gridview.height, true); } @@ -1290,6 +1362,7 @@ export class DockviewComponent locked: !!locked, hideHeader: !!hideHeader, }); + this._onDidAddGroup.fire(group); const createdPanels: IDockviewPanel[] = []; @@ -1306,8 +1379,6 @@ export class DockviewComponent createdPanels.push(panel); } - this._onDidAddGroup.fire(group); - for (let i = 0; i < views.length; i++) { const panel = createdPanels[i]; @@ -1394,6 +1465,7 @@ export class DockviewComponent 'dockview: failed to deserialize layout. Reverting changes', err ); + /** * Takes all the successfully created groups and remove all of their panels. */ @@ -1704,24 +1776,24 @@ export class DockviewComponent (x) => x.api.location.type === 'grid' && x.api.isVisible ).length === 0 ) { - if (!this.watermark) { - this.watermark = this.createWatermarkComponent(); + if (!this._watermark) { + this._watermark = this.createWatermarkComponent(); - this.watermark.init({ + this._watermark.init({ containerApi: new DockviewApi(this), }); const watermarkContainer = document.createElement('div'); watermarkContainer.className = 'dv-watermark-container'; addTestId(watermarkContainer, 'watermark-component'); - watermarkContainer.appendChild(this.watermark.element); + watermarkContainer.appendChild(this._watermark.element); this.gridview.element.appendChild(watermarkContainer); } - } else if (this.watermark) { - this.watermark.element.parentElement!.remove(); - this.watermark.dispose?.(); - this.watermark = null; + } else if (this._watermark) { + this._watermark.element.parentElement!.remove(); + this._watermark.dispose?.(); + this._watermark = null; } } @@ -1885,7 +1957,7 @@ export class DockviewComponent const refGroup = selectedGroup.referenceGroup ? this.getPanel(selectedGroup.referenceGroup) : undefined; - if (refGroup) { + if (refGroup && refGroup.panels.length === 0) { this.removeGroup(refGroup); } } @@ -2081,9 +2153,7 @@ export class DockviewComponent const newGroup = this.createGroupAtLocation(targetLocation); this.movingLock(() => - newGroup.model.openPanel(removedPanel, { - skipSetActive: true, - }) + newGroup.model.openPanel(removedPanel) ); this.doSetGroupAndPanelActive(newGroup); @@ -2365,9 +2435,11 @@ export class DockviewComponent if (this._moving) { return; } + if (event.panel !== this.activePanel) { return; } + if (this._onDidActivePanelChange.value !== event.panel) { this._onDidActivePanelChange.fire(event.panel); } @@ -2450,4 +2522,44 @@ export class DockviewComponent ? rootOrientation : orthogonal(rootOrientation); } + + private updateDropTargetModel(options: Partial) { + if ('dndEdges' in options) { + this._rootDropTarget.disabled = + typeof options.dndEdges === 'boolean' && + options.dndEdges === false; + + if ( + typeof options.dndEdges === 'object' && + options.dndEdges !== null + ) { + this._rootDropTarget.setOverlayModel(options.dndEdges); + } else { + this._rootDropTarget.setOverlayModel( + DEFAULT_ROOT_OVERLAY_MODEL + ); + } + } + + if ('rootOverlayModel' in options) { + this.updateDropTargetModel({ dndEdges: options.dndEdges }); + } + } + + private updateTheme(): void { + const theme = this._options.theme ?? themeAbyss; + this._themeClassnames.setClassNames(theme.className); + + this.gridview.margin = theme.gap ?? 0; + + switch (theme.dndOverlayMounting) { + case 'absolute': + this.rootDropTargetContainer.disabled = false; + break; + case 'relative': + default: + this.rootDropTargetContainer.disabled = true; + break; + } + } } diff --git a/packages/dockview-core/src/dockview/dockviewGroupPanel.ts b/packages/dockview-core/src/dockview/dockviewGroupPanel.ts index a3e714829..fd5e70fe3 100644 --- a/packages/dockview-core/src/dockview/dockviewGroupPanel.ts +++ b/packages/dockview-core/src/dockview/dockviewGroupPanel.ts @@ -124,6 +124,12 @@ export class DockviewGroupPanel options, this ); + + this.addDisposables( + this.model.onDidActivePanelChange((event) => { + this.api._onDidActivePanelChange.fire(event); + }) + ); } override focus(): void { diff --git a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts index 17af6e573..c56babf91 100644 --- a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts +++ b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts @@ -39,6 +39,7 @@ import { import { OverlayRenderContainer } from '../overlay/overlayRenderContainer'; import { TitleEvent } from '../api/dockviewPanelApi'; import { Contraints } from '../gridview/gridviewPanel'; +import { DropTargetAnchorContainer } from '../dnd/dropTargetAnchorContainer'; interface GroupMoveEvent { groupId: string; @@ -265,6 +266,8 @@ export class DockviewGroupPanelModel private mostRecentlyUsed: IDockviewPanel[] = []; private _overwriteRenderContainer: OverlayRenderContainer | null = null; + private _overwriteDropTargetContainer: DropTargetAnchorContainer | null = + null; private readonly _onDidChange = new Emitter(); readonly onDidChange: Event = @@ -506,7 +509,9 @@ export class DockviewGroupPanelModel this._onDidAddPanel, this._onDidRemovePanel, this._onDidActivePanelChange, - this._onUnhandledDragOverEvent + this._onUnhandledDragOverEvent, + this._onDidPanelTitleChange, + this._onDidPanelParametersChange ); } @@ -533,6 +538,17 @@ export class DockviewGroupPanelModel ); } + set dropTargetContainer(value: DropTargetAnchorContainer | null) { + this._overwriteDropTargetContainer = value; + } + + get dropTargetContainer(): DropTargetAnchorContainer | null { + return ( + this._overwriteDropTargetContainer ?? + this.accessor.rootDropTargetContainer + ); + } + initialize(): void { if (this.options.panels) { this.options.panels.forEach((panel) => { @@ -1047,6 +1063,29 @@ export class DockviewGroupPanelModel const data = getPanelData(); if (data && data.viewId === this.accessor.id) { + if (type === 'content') { + if (data.groupId === this.id) { + // don't allow to drop on self for center position + + if (position === 'center') { + return; + } + + if (data.panelId === null) { + // don't allow group move to drop anywhere on self + return; + } + } + } + + if (type === 'header') { + if (data.groupId === this.id) { + if (data.panelId === null) { + return; + } + } + } + if (data.panelId === null) { // this is a group move dnd event const { groupId } = data; diff --git a/packages/dockview-core/src/dockview/dockviewPanelModel.ts b/packages/dockview-core/src/dockview/dockviewPanelModel.ts index 950b4d577..903fcc095 100644 --- a/packages/dockview-core/src/dockview/dockviewPanelModel.ts +++ b/packages/dockview-core/src/dockview/dockviewPanelModel.ts @@ -4,10 +4,10 @@ import { IContentRenderer, ITabRenderer, } from './types'; -import { DockviewGroupPanel } from './dockviewGroupPanel'; import { IDisposable } from '../lifecycle'; import { IDockviewComponent } from './dockviewComponent'; import { PanelUpdateEvent } from '../panel/types'; +import { TabLocation } from './framework'; export interface IDockviewPanelModel extends IDisposable { readonly contentComponent: string; @@ -17,13 +17,16 @@ export interface IDockviewPanelModel extends IDisposable { update(event: PanelUpdateEvent): void; layout(width: number, height: number): void; init(params: GroupPanelPartInitParameters): void; - updateParentGroup(group: DockviewGroupPanel, isPanelVisible: boolean): void; + createTabRenderer(tabLocation: TabLocation): ITabRenderer; } export class DockviewPanelModel implements IDockviewPanelModel { private readonly _content: IContentRenderer; private readonly _tab: ITabRenderer; + private _params: GroupPanelPartInitParameters | undefined; + private _updateEvent: PanelUpdateEvent | undefined; + get content(): IContentRenderer { return this._content; } @@ -42,16 +45,23 @@ export class DockviewPanelModel implements IDockviewPanelModel { this._tab = this.createTabComponent(this.id, tabComponent); } - init(params: GroupPanelPartInitParameters): void { - this.content.init(params); - this.tab.init(params); + createTabRenderer(tabLocation: TabLocation): ITabRenderer { + const cmp = this.createTabComponent(this.id, this.tabComponent); + if (this._params) { + cmp.init({ ...this._params, tabLocation }); + } + if (this._updateEvent) { + cmp.update?.(this._updateEvent); + } + + return cmp; } - updateParentGroup( - _group: DockviewGroupPanel, - _isPanelVisible: boolean - ): void { - // noop + init(params: GroupPanelPartInitParameters): void { + this._params = params; + + this.content.init(params); + this.tab.init({ ...params, tabLocation: 'header' }); } layout(width: number, height: number): void { @@ -59,6 +69,8 @@ export class DockviewPanelModel implements IDockviewPanelModel { } update(event: PanelUpdateEvent): void { + this._updateEvent = event; + this.content.update?.(event); this.tab.update?.(event); } diff --git a/packages/dockview-core/src/dockview/framework.ts b/packages/dockview-core/src/dockview/framework.ts index bd6ccaff5..1ed239b46 100644 --- a/packages/dockview-core/src/dockview/framework.ts +++ b/packages/dockview-core/src/dockview/framework.ts @@ -11,9 +11,11 @@ export interface IGroupPanelBaseProps containerApi: DockviewApi; } +export type TabLocation = 'header' | 'headerOverflow'; + export type IDockviewPanelHeaderProps< T extends { [index: string]: any } = any -> = IGroupPanelBaseProps; +> = IGroupPanelBaseProps & { tabLocation: TabLocation }; export type IDockviewPanelProps = IGroupPanelBaseProps; diff --git a/packages/dockview-core/src/dockview/options.ts b/packages/dockview-core/src/dockview/options.ts index 3e87923a2..86546934e 100644 --- a/packages/dockview-core/src/dockview/options.ts +++ b/packages/dockview-core/src/dockview/options.ts @@ -16,6 +16,8 @@ import { DockviewPanelRenderer } from '../overlay/overlayRenderContainer'; import { IGroupHeaderProps } from './framework'; import { FloatingGroupOptions } from './dockviewComponent'; import { Contraints } from '../gridview/gridviewPanel'; +import { AcceptableEvent, IAcceptableEvent } from '../events'; +import { DockviewTheme } from './theme'; export interface IHeaderActionsRenderer extends IDisposable { readonly element: HTMLElement; @@ -51,52 +53,48 @@ export interface DockviewOptions { popoutUrl?: string; defaultRenderer?: DockviewPanelRenderer; debug?: boolean; - rootOverlayModel?: DroptargetOverlayModel; - locked?: boolean; - disableDnd?: boolean; - className?: string; + // #start dnd + dndEdges?: false | DroptargetOverlayModel; /** - * Pixel gap between groups - */ - gap?: number; + * @deprecated use `dndEdges` instead. To be removed in a future version. + * */ + rootOverlayModel?: DroptargetOverlayModel; + disableDnd?: boolean; + // #end dnd + locked?: boolean; + className?: string; /** * Define the behaviour of the dock when there are no panels to display. Defaults to `watermark`. */ noPanelsOverlay?: 'emptyGroup' | 'watermark'; + theme?: DockviewTheme; + disableTabsOverflowList?: boolean; } -export interface DockviewDndOverlayEvent { +export interface DockviewDndOverlayEvent extends IAcceptableEvent { nativeEvent: DragEvent; target: DockviewGroupDropLocation; position: Position; group?: DockviewGroupPanel; getData: () => PanelTransfer | undefined; - // - isAccepted: boolean; - accept(): void; } -export class DockviewUnhandledDragOverEvent implements DockviewDndOverlayEvent { - private _isAccepted = false; - - get isAccepted(): boolean { - return this._isAccepted; - } - +export class DockviewUnhandledDragOverEvent + extends AcceptableEvent + implements DockviewDndOverlayEvent +{ constructor( readonly nativeEvent: DragEvent, readonly target: DockviewGroupDropLocation, readonly position: Position, readonly getData: () => PanelTransfer | undefined, readonly group?: DockviewGroupPanel - ) {} - - accept(): void { - this._isAccepted = true; + ) { + super(); } } -export const PROPERTY_KEYS: (keyof DockviewOptions)[] = (() => { +export const PROPERTY_KEYS_DOCKVIEW: (keyof DockviewOptions)[] = (() => { /** * by readong the keys from an empty value object TypeScript will error * when we add or remove new properties to `DockviewOptions` @@ -113,9 +111,11 @@ export const PROPERTY_KEYS: (keyof DockviewOptions)[] = (() => { rootOverlayModel: undefined, locked: undefined, disableDnd: undefined, - gap: undefined, className: undefined, noPanelsOverlay: undefined, + dndEdges: undefined, + theme: undefined, + disableTabsOverflowList: undefined, }; return Object.keys(properties) as (keyof DockviewOptions)[]; diff --git a/packages/dockview-core/src/dockview/strictEventsSequencing.ts b/packages/dockview-core/src/dockview/strictEventsSequencing.ts new file mode 100644 index 000000000..60f91e613 --- /dev/null +++ b/packages/dockview-core/src/dockview/strictEventsSequencing.ts @@ -0,0 +1,54 @@ +import { CompositeDisposable } from '../lifecycle'; +import { DockviewComponent } from './dockviewComponent'; + +export class StrictEventsSequencing extends CompositeDisposable { + constructor(private readonly accessor: DockviewComponent) { + super(); + + this.init(); + } + + private init(): void { + const panels = new Set(); + const groups = new Set(); + + this.addDisposables( + this.accessor.onDidAddPanel((panel) => { + if (panels.has(panel.api.id)) { + throw new Error( + `dockview: Invalid event sequence. [onDidAddPanel] called for panel ${panel.api.id} but panel already exists` + ); + } else { + panels.add(panel.api.id); + } + }), + this.accessor.onDidRemovePanel((panel) => { + if (!panels.has(panel.api.id)) { + throw new Error( + `dockview: Invalid event sequence. [onDidRemovePanel] called for panel ${panel.api.id} but panel does not exists` + ); + } else { + panels.delete(panel.api.id); + } + }), + this.accessor.onDidAddGroup((group) => { + if (groups.has(group.api.id)) { + throw new Error( + `dockview: Invalid event sequence. [onDidAddGroup] called for group ${group.api.id} but group already exists` + ); + } else { + groups.add(group.api.id); + } + }), + this.accessor.onDidRemoveGroup((group) => { + if (!groups.has(group.api.id)) { + throw new Error( + `dockview: Invalid event sequence. [onDidRemoveGroup] called for group ${group.api.id} but group does not exists` + ); + } else { + groups.delete(group.api.id); + } + }) + ); + } +} diff --git a/packages/dockview-core/src/dockview/theme.ts b/packages/dockview-core/src/dockview/theme.ts new file mode 100644 index 000000000..c010b84d0 --- /dev/null +++ b/packages/dockview-core/src/dockview/theme.ts @@ -0,0 +1,70 @@ +export interface DockviewTheme { + /** + * The name of the theme + */ + name: string; + /** + * The class name to apply to the theme containing the CSS variables settings. + */ + className: string; + /** + * The gap between the groups + */ + gap?: number; + /** + * The mouting position of the overlay shown when dragging a panel. `absolute` + * will mount the overlay to root of the dockview component whereas `relative` will mount the overlay to the group container. + */ + dndOverlayMounting?: 'absolute' | 'relative'; + /** + * When dragging a panel, the overlay can either encompass the panel contents or the entire group including the tab header space. + */ + dndPanelOverlay?: 'content' | 'group'; +} + +export const themeDark: DockviewTheme = { + name: 'dark', + className: 'dockview-theme-dark', +}; + +export const themeLight: DockviewTheme = { + name: 'light', + className: 'dockview-theme-light', +}; + +export const themeVisualStudio: DockviewTheme = { + name: 'visualStudio', + className: 'dockview-theme-vs', +}; + +export const themeAbyss: DockviewTheme = { + name: 'abyss', + className: 'dockview-theme-abyss', +}; + +export const themeDracula: DockviewTheme = { + name: 'dracula', + className: 'dockview-theme-dracula', +}; + +export const themeReplit: DockviewTheme = { + name: 'replit', + className: 'dockview-theme-replit', + gap: 10, +}; + +export const themeAbyssSpaced: DockviewTheme = { + name: 'abyssSpaced', + className: 'dockview-theme-abyss-spaced', + gap: 10, + dndOverlayMounting: 'absolute', + dndPanelOverlay: 'group', +}; + +export const themeLightSpaced: DockviewTheme = { + name: 'lightSpaced', + className: 'dockview-theme-light-spaced', + gap: 10, + dndOverlayMounting: 'absolute', + dndPanelOverlay: 'group', +}; diff --git a/packages/dockview-core/src/dockview/types.ts b/packages/dockview-core/src/dockview/types.ts index 3b2c8f112..5706f0721 100644 --- a/packages/dockview-core/src/dockview/types.ts +++ b/packages/dockview-core/src/dockview/types.ts @@ -4,6 +4,7 @@ import { DockviewApi } from '../api/component.api'; import { Optional } from '../types'; import { IDockviewGroupPanel } from './dockviewGroupPanel'; import { DockviewPanelRenderer } from '../overlay/overlayRenderContainer'; +import { TabLocation } from './framework'; export interface HeaderPartInitParameters { title: string; @@ -34,10 +35,14 @@ export interface IWatermarkRenderer init: (params: WatermarkRendererInitParameters) => void; } +export interface TabPartInitParameters extends GroupPanelPartInitParameters { + tabLocation: TabLocation; +} + export interface ITabRenderer extends Optional, RendererMethodOptionalList> { readonly element: HTMLElement; - init(parameters: GroupPanelPartInitParameters): void; + init(parameters: TabPartInitParameters): void; } export interface IContentRenderer diff --git a/packages/dockview-core/src/dom.ts b/packages/dockview-core/src/dom.ts index 99e7b2bee..4d06ffa53 100644 --- a/packages/dockview-core/src/dom.ts +++ b/packages/dockview-core/src/dom.ts @@ -379,3 +379,25 @@ export class Classnames { } } } + +export function isChildEntirelyVisibleWithinParent( + child: HTMLElement, + parent: HTMLElement +): boolean { + // + const childPosition = getDomNodePagePosition(child); + const parentPosition = getDomNodePagePosition(parent); + + if (childPosition.left < parentPosition.left) { + return false; + } + + if ( + childPosition.left + childPosition.width > + parentPosition.left + parentPosition.width + ) { + return false; + } + + return true; +} diff --git a/packages/dockview-core/src/events.ts b/packages/dockview-core/src/events.ts index 5c7d0d260..d9cbf32d6 100644 --- a/packages/dockview-core/src/events.ts +++ b/packages/dockview-core/src/events.ts @@ -41,6 +41,23 @@ export class DockviewEvent implements IDockviewEvent { } } +export interface IAcceptableEvent { + readonly isAccepted: boolean; + accept(): void; +} + +export class AcceptableEvent implements IAcceptableEvent { + private _isAccepted = false; + + get isAccepted(): boolean { + return this._isAccepted; + } + + accept(): void { + this._isAccepted = true; + } +} + class LeakageMonitor { readonly events = new Map, Stacktrace>(); diff --git a/packages/dockview-core/src/gridview/baseComponentGridview.ts b/packages/dockview-core/src/gridview/baseComponentGridview.ts index 44e810a50..2ef754396 100644 --- a/packages/dockview-core/src/gridview/baseComponentGridview.ts +++ b/packages/dockview-core/src/gridview/baseComponentGridview.ts @@ -11,6 +11,9 @@ import { Classnames } from '../dom'; const nextLayoutId = sequentialNumberGenerator(); +/** + * A direction in which a panel can be moved or placed relative to another panel. + */ export type Direction = 'left' | 'right' | 'above' | 'below' | 'within'; export function toTarget(direction: Direction): Position { @@ -155,7 +158,7 @@ export abstract class BaseGrid this.gridview.locked = value; } - constructor(parentElement: HTMLElement, options: BaseGridOptions) { + constructor(container: HTMLElement, options: BaseGridOptions) { super(document.createElement('div'), options.disableAutoResizing); this.element.style.height = '100%'; this.element.style.width = '100%'; @@ -163,7 +166,8 @@ export abstract class BaseGrid this._classNames = new Classnames(this.element); this._classNames.setClassNames(options.className ?? ''); - parentElement.appendChild(this.element); + // the container is owned by the third-party, do not modify/delete it + container.appendChild(this.element); this.gridview = new Gridview( !!options.proportionalLayout, @@ -205,6 +209,8 @@ export abstract class BaseGrid )(() => { this._bufferOnDidLayoutChange.fire(); }), + this._onDidMaximizedChange, + this._onDidViewVisibilityChangeMicroTaskQueue, this._bufferOnDidLayoutChange ); } diff --git a/packages/dockview-core/src/gridview/gridviewComponent.ts b/packages/dockview-core/src/gridview/gridviewComponent.ts index a0ea22288..80df754bd 100644 --- a/packages/dockview-core/src/gridview/gridviewComponent.ts +++ b/packages/dockview-core/src/gridview/gridviewComponent.ts @@ -23,7 +23,6 @@ import { } from './gridviewPanel'; import { BaseComponentOptions, Parameters } from '../panel/types'; import { Orientation, Sizing } from '../splitview/splitview'; -import { createComponent } from '../panel/componentFactory'; import { Emitter, Event } from '../events'; import { Position } from '../dnd/droptarget'; @@ -114,11 +113,13 @@ export class GridviewComponent this._deserializer = value; } - constructor(parentElement: HTMLElement, options: GridviewComponentOptions) { - super(parentElement, { - proportionalLayout: options.proportionalLayout, + constructor(container: HTMLElement, options: GridviewComponentOptions) { + super(container, { + proportionalLayout: options.proportionalLayout ?? true, orientation: options.orientation, - styles: options.styles, + styles: options.hideBorders + ? { separatorBorder: 'transparent' } + : undefined, disableAutoResizing: options.disableAutoResizing, className: options.className, }); @@ -139,13 +140,6 @@ export class GridviewComponent this._onDidActiveGroupChange.fire(event); }) ); - - if (!this.options.components) { - this.options.components = {}; - } - if (!this.options.frameworkComponents) { - this.options.frameworkComponents = {}; - } } override updateOptions(options: Partial): void { @@ -216,19 +210,11 @@ export class GridviewComponent this.gridview.deserialize(grid, { fromJSON: (node) => { const { data } = node; - const view = createComponent( - data.id, - data.component, - this.options.components ?? {}, - this.options.frameworkComponents ?? {}, - this.options.frameworkComponentFactory - ? { - createComponent: - this.options.frameworkComponentFactory - .createComponent, - } - : undefined - ); + + const view = this.options.createComponent({ + id: data.id, + name: data.component, + }); queue.push(() => view.init({ @@ -363,19 +349,10 @@ export class GridviewComponent } } - const view = createComponent( - options.id, - options.component, - this.options.components ?? {}, - this.options.frameworkComponents ?? {}, - this.options.frameworkComponentFactory - ? { - createComponent: - this.options.frameworkComponentFactory - .createComponent, - } - : undefined - ); + const view = this.options.createComponent({ + id: options.id, + name: options.component, + }); view.init({ params: options.params ?? {}, diff --git a/packages/dockview-core/src/gridview/options.ts b/packages/dockview-core/src/gridview/options.ts index 8a5025846..ba96378bc 100644 --- a/packages/dockview-core/src/gridview/options.ts +++ b/packages/dockview-core/src/gridview/options.ts @@ -1,21 +1,34 @@ import { GridviewPanel } from './gridviewPanel'; -import { ISplitviewStyles, Orientation } from '../splitview/splitview'; -import { - ComponentConstructor, - FrameworkFactory, -} from '../panel/componentFactory'; +import { Orientation } from '../splitview/splitview'; +import { CreateComponentOptions } from '../dockview/options'; -export interface GridviewComponentOptions { +export interface GridviewOptions { disableAutoResizing?: boolean; - proportionalLayout: boolean; + proportionalLayout?: boolean; orientation: Orientation; - components?: { - [componentName: string]: ComponentConstructor; - }; - frameworkComponents?: { - [componentName: string]: any; - }; - frameworkComponentFactory?: FrameworkFactory; - styles?: ISplitviewStyles; className?: string; + hideBorders?: boolean; } + +export interface GridviewFrameworkOptions { + createComponent: (options: CreateComponentOptions) => GridviewPanel; +} + +export type GridviewComponentOptions = GridviewOptions & + GridviewFrameworkOptions; + +export const PROPERTY_KEYS_GRIDVIEW: (keyof GridviewOptions)[] = (() => { + /** + * by readong the keys from an empty value object TypeScript will error + * when we add or remove new properties to `DockviewOptions` + */ + const properties: Record = { + disableAutoResizing: undefined, + proportionalLayout: undefined, + orientation: undefined, + hideBorders: undefined, + className: undefined, + }; + + return Object.keys(properties) as (keyof GridviewOptions)[]; +})(); diff --git a/packages/dockview-core/src/index.ts b/packages/dockview-core/src/index.ts index 59d4a19e6..d9f66a787 100644 --- a/packages/dockview-core/src/index.ts +++ b/packages/dockview-core/src/index.ts @@ -24,16 +24,24 @@ export * from './splitview/splitview'; export { SplitviewComponentOptions, PanelViewInitParameters, + SplitviewOptions, + SplitviewFrameworkOptions, + PROPERTY_KEYS_SPLITVIEW, } from './splitview/options'; export * from './paneview/paneview'; export * from './gridview/gridview'; -export { GridviewComponentOptions } from './gridview/options'; +export { + GridviewComponentOptions, + GridviewOptions, + GridviewFrameworkOptions, + PROPERTY_KEYS_GRIDVIEW, +} from './gridview/options'; export * from './gridview/baseComponentGridview'; export { DraggablePaneviewPanel, - PaneviewDropEvent, + PaneviewDidDropEvent as PaneviewDropEvent, } from './paneview/draggablePaneviewPanel'; export * from './dockview/components/panel/content'; @@ -56,6 +64,7 @@ export { } from './dockview/framework'; export * from './dockview/options'; +export * from './dockview/theme'; export * from './dockview/dockviewPanel'; export { DefaultTab } from './dockview/components/tab/defaultTab'; export { @@ -67,7 +76,14 @@ export * from './dockview/dockviewComponent'; export * from './gridview/gridviewComponent'; export * from './splitview/splitviewComponent'; export * from './paneview/paneviewComponent'; -export { PaneviewComponentOptions } from './paneview/options'; +export { + PaneviewComponentOptions, + PaneviewOptions, + PaneviewFrameworkOptions, + PROPERTY_KEYS_PANEVIEW, + PaneviewUnhandledDragOverEvent, + PaneviewDndOverlayEvent, +} from './paneview/options'; export * from './gridview/gridviewPanel'; export { SplitviewPanel, ISplitviewPanel } from './splitview/splitviewPanel'; diff --git a/packages/dockview-core/src/panel/componentFactory.ts b/packages/dockview-core/src/panel/componentFactory.ts deleted file mode 100644 index fb77f9944..000000000 --- a/packages/dockview-core/src/panel/componentFactory.ts +++ /dev/null @@ -1,58 +0,0 @@ -export interface FrameworkFactory { - createComponent: (id: string, componentId: string, component: any) => T; -} - -export type ComponentConstructor = { - new (id: string, component: string): T; -}; - -export function createComponent( - id: string, - componentName?: string, - components: { - [componentName: string]: ComponentConstructor; - } = {}, - frameworkComponents: { - [componentName: string]: any; - } = {}, - createFrameworkComponent?: FrameworkFactory, - fallback?: () => T -): T { - const Component = - typeof componentName === 'string' - ? components[componentName] - : undefined; - const FrameworkComponent = - typeof componentName === 'string' - ? frameworkComponents[componentName] - : undefined; - - if (Component && FrameworkComponent) { - throw new Error( - `Cannot create '${id}'. component '${componentName}' registered as both a component and frameworkComponent` - ); - } - if (FrameworkComponent) { - if (!createFrameworkComponent) { - throw new Error( - `Cannot create '${id}' for framework component '${componentName}'. you must register a frameworkPanelWrapper to use framework components` - ); - } - return createFrameworkComponent.createComponent( - id, - componentName!, - FrameworkComponent - ); - } - - if (!Component) { - if (fallback) { - return fallback(); - } - throw new Error( - `Cannot create '${id}', no component '${componentName}' provided` - ); - } - - return new Component(id, componentName!); -} diff --git a/packages/dockview-core/src/paneview/defaultPaneviewHeader.ts b/packages/dockview-core/src/paneview/defaultPaneviewHeader.ts index ee37cc597..c53121154 100644 --- a/packages/dockview-core/src/paneview/defaultPaneviewHeader.ts +++ b/packages/dockview-core/src/paneview/defaultPaneviewHeader.ts @@ -2,14 +2,11 @@ import { addDisposableListener } from '../events'; import { PaneviewPanelApiImpl } from '../api/paneviewPanelApi'; import { CompositeDisposable, MutableDisposable } from '../lifecycle'; import { PanelUpdateEvent } from '../panel/types'; -import { IPaneHeaderPart, PanePanelInitParameter } from './paneviewPanel'; +import { IPanePart, PanePanelInitParameter } from './paneviewPanel'; import { toggleClass } from '../dom'; import { createChevronRightButton, createExpandMoreButton } from '../svg'; -export class DefaultHeader - extends CompositeDisposable - implements IPaneHeaderPart -{ +export class DefaultHeader extends CompositeDisposable implements IPanePart { private readonly _expandedIcon = createExpandMoreButton(); private readonly _collapsedIcon = createChevronRightButton(); private readonly disposable = new MutableDisposable(); diff --git a/packages/dockview-core/src/paneview/draggablePaneviewPanel.ts b/packages/dockview-core/src/paneview/draggablePaneviewPanel.ts index b86c06d33..7227ea46a 100644 --- a/packages/dockview-core/src/paneview/draggablePaneviewPanel.ts +++ b/packages/dockview-core/src/paneview/draggablePaneviewPanel.ts @@ -6,9 +6,13 @@ import { PaneTransfer, } from '../dnd/dataTransfer'; import { Droptarget, DroptargetEvent } from '../dnd/droptarget'; -import { Emitter } from '../events'; +import { Emitter, Event } from '../events'; import { IDisposable } from '../lifecycle'; import { Orientation } from '../splitview/splitview'; +import { + PaneviewDndOverlayEvent, + PaneviewUnhandledDragOverEvent, +} from './options'; import { IPaneviewComponent } from './paneviewComponent'; import { IPaneviewPanel, @@ -16,7 +20,7 @@ import { PaneviewPanel, } from './paneviewPanel'; -export interface PaneviewDropEvent extends DroptargetEvent { +export interface PaneviewDidDropEvent extends DroptargetEvent { panel: IPaneviewPanel; getData: () => PaneTransfer | undefined; api: PaneviewApi; @@ -26,9 +30,14 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel { private handler: DragHandler | undefined; private target: Droptarget | undefined; - private readonly _onDidDrop = new Emitter(); + private readonly _onDidDrop = new Emitter(); readonly onDidDrop = this._onDidDrop.event; + private readonly _onUnhandledDragOverEvent = + new Emitter(); + readonly onUnhandledDragOverEvent: Event = + this._onUnhandledDragOverEvent.event; + constructor( private readonly accessor: IPaneviewComponent, id: string, @@ -40,6 +49,8 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel { ) { super(id, component, headerComponent, orientation, isExpanded, true); + this.addDisposables(this._onDidDrop, this._onUnhandledDragOverEvent); + if (!disableDnd) { this.initDragFeatures(); } @@ -76,7 +87,7 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel { overlayModel: { activationSize: { type: 'percentage', value: 50 }, }, - canDisplayOverlay: (event) => { + canDisplayOverlay: (event, position) => { const data = getPaneData(); if (data) { @@ -88,15 +99,16 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel { } } - if (this.accessor.options.showDndOverlay) { - return this.accessor.options.showDndOverlay({ - nativeEvent: event, - getData: getPaneData, - panel: this, - }); - } + const firedEvent = new PaneviewUnhandledDragOverEvent( + event, + position, + getPaneData, + this + ); - return false; + this._onUnhandledDragOverEvent.fire(firedEvent); + + return firedEvent.isAccepted; }, }); diff --git a/packages/dockview-core/src/paneview/options.ts b/packages/dockview-core/src/paneview/options.ts index 8aef3e649..f51e28dce 100644 --- a/packages/dockview-core/src/paneview/options.ts +++ b/packages/dockview-core/src/paneview/options.ts @@ -1,29 +1,56 @@ -import { - ComponentConstructor, - FrameworkFactory, -} from '../panel/componentFactory'; -import { PaneviewDndOverlayEvent } from './paneviewComponent'; -import { IPaneBodyPart, IPaneHeaderPart, PaneviewPanel } from './paneviewPanel'; +import { PaneTransfer } from '../dnd/dataTransfer'; +import { Position } from '../dnd/droptarget'; +import { CreateComponentOptions } from '../dockview/options'; +import { AcceptableEvent, IAcceptableEvent } from '../events'; +import { IPanePart, IPaneviewPanel } from './paneviewPanel'; -export interface PaneviewComponentOptions { +export interface PaneviewOptions { disableAutoResizing?: boolean; - components?: { - [componentName: string]: ComponentConstructor; - }; - frameworkComponents?: { - [componentName: string]: any; - }; - headerComponents?: { - [componentName: string]: ComponentConstructor; - }; - headerframeworkComponents?: { - [componentName: string]: any; - }; - frameworkWrapper?: { - header: FrameworkFactory; - body: FrameworkFactory; - }; disableDnd?: boolean; - showDndOverlay?: (event: PaneviewDndOverlayEvent) => boolean; className?: string; } + +export interface PaneviewFrameworkOptions { + createComponent: (options: CreateComponentOptions) => IPanePart; + createHeaderComponent?: ( + options: CreateComponentOptions + ) => IPanePart | undefined; +} + +export type PaneviewComponentOptions = PaneviewOptions & + PaneviewFrameworkOptions; + +export const PROPERTY_KEYS_PANEVIEW: (keyof PaneviewOptions)[] = (() => { + /** + * by readong the keys from an empty value object TypeScript will error + * when we add or remove new properties to `DockviewOptions` + */ + const properties: Record = { + disableAutoResizing: undefined, + disableDnd: undefined, + className: undefined, + }; + + return Object.keys(properties) as (keyof PaneviewOptions)[]; +})(); + +export interface PaneviewDndOverlayEvent extends IAcceptableEvent { + nativeEvent: DragEvent; + position: Position; + panel: IPaneviewPanel; + getData: () => PaneTransfer | undefined; +} + +export class PaneviewUnhandledDragOverEvent + extends AcceptableEvent + implements PaneviewDndOverlayEvent +{ + constructor( + readonly nativeEvent: DragEvent, + readonly position: Position, + readonly getData: () => PaneTransfer | undefined, + readonly panel: IPaneviewPanel + ) { + super(); + } +} diff --git a/packages/dockview-core/src/paneview/paneviewComponent.ts b/packages/dockview-core/src/paneview/paneviewComponent.ts index 171f4a703..4856fb1ad 100644 --- a/packages/dockview-core/src/paneview/paneviewComponent.ts +++ b/packages/dockview-core/src/paneview/paneviewComponent.ts @@ -1,5 +1,4 @@ import { PaneviewApi } from '../api/component.api'; -import { createComponent } from '../panel/componentFactory'; import { Emitter, Event } from '../events'; import { CompositeDisposable, @@ -7,33 +6,21 @@ import { MutableDisposable, } from '../lifecycle'; import { LayoutPriority, Orientation, Sizing } from '../splitview/splitview'; -import { PaneviewComponentOptions } from './options'; +import { PaneviewComponentOptions, PaneviewDndOverlayEvent } from './options'; import { Paneview } from './paneview'; -import { - IPaneBodyPart, - IPaneHeaderPart, - PaneviewPanel, - IPaneviewPanel, -} from './paneviewPanel'; +import { IPanePart, PaneviewPanel, IPaneviewPanel } from './paneviewPanel'; import { DraggablePaneviewPanel, - PaneviewDropEvent, + PaneviewDidDropEvent, } from './draggablePaneviewPanel'; import { DefaultHeader } from './defaultPaneviewHeader'; import { sequentialNumberGenerator } from '../math'; -import { PaneTransfer } from '../dnd/dataTransfer'; import { Resizable } from '../resizable'; import { Parameters } from '../panel/types'; import { Classnames } from '../dom'; const nextLayoutId = sequentialNumberGenerator(); -export interface PaneviewDndOverlayEvent { - nativeEvent: DragEvent; - panel: IPaneviewPanel; - getData: () => PaneTransfer | undefined; -} - export interface SerializedPaneviewPanel { snap?: boolean; priority?: LayoutPriority; @@ -61,8 +48,8 @@ export class PaneFramework extends DraggablePaneviewPanel { id: string; component: string; headerComponent: string | undefined; - body: IPaneBodyPart; - header: IPaneHeaderPart; + body: IPanePart; + header: IPanePart; orientation: Orientation; isExpanded: boolean; disableDnd: boolean; @@ -112,9 +99,10 @@ export interface IPaneviewComponent extends IDisposable { readonly options: PaneviewComponentOptions; readonly onDidAddView: Event; readonly onDidRemoveView: Event; - readonly onDidDrop: Event; + readonly onDidDrop: Event; readonly onDidLayoutChange: Event; readonly onDidLayoutFromJSON: Event; + readonly onUnhandledDragOverEvent: Event; addPanel( options: AddPaneviewComponentOptions ): IPaneviewPanel; @@ -143,8 +131,8 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent { private readonly _onDidLayoutChange = new Emitter(); readonly onDidLayoutChange: Event = this._onDidLayoutChange.event; - private readonly _onDidDrop = new Emitter(); - readonly onDidDrop: Event = this._onDidDrop.event; + private readonly _onDidDrop = new Emitter(); + readonly onDidDrop: Event = this._onDidDrop.event; private readonly _onDidAddView = new Emitter(); readonly onDidAddView = this._onDidAddView.event; @@ -152,6 +140,11 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent { private readonly _onDidRemoveView = new Emitter(); readonly onDidRemoveView = this._onDidRemoveView.event; + private readonly _onUnhandledDragOverEvent = + new Emitter(); + readonly onUnhandledDragOverEvent: Event = + this._onUnhandledDragOverEvent.event; + private readonly _classNames: Classnames; get id(): string { @@ -202,28 +195,27 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent { return this._options; } - constructor(parentElement: HTMLElement, options: PaneviewComponentOptions) { - super(parentElement, options.disableAutoResizing); + constructor(container: HTMLElement, options: PaneviewComponentOptions) { + super(document.createElement('div'), options.disableAutoResizing); + this.element.style.height = '100%'; + this.element.style.width = '100%'; this.addDisposables( this._onDidLayoutChange, this._onDidLayoutfromJSON, this._onDidDrop, this._onDidAddView, - this._onDidRemoveView + this._onDidRemoveView, + this._onUnhandledDragOverEvent ); this._classNames = new Classnames(this.element); this._classNames.setClassNames(options.className ?? ''); - this._options = options; + // the container is owned by the third-party, do not modify/delete it + container.appendChild(this.element); - if (!options.components) { - options.components = {}; - } - if (!options.frameworkComponents) { - options.frameworkComponents = {}; - } + this._options = options; this.paneview = new Paneview(this.element, { // only allow paneview in the vertical orientation for now @@ -257,36 +249,21 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent { addPanel( options: AddPaneviewComponentOptions ): IPaneviewPanel { - const body = createComponent( - options.id, - options.component, - this.options.components ?? {}, - this.options.frameworkComponents ?? {}, - this.options.frameworkWrapper - ? { - createComponent: - this.options.frameworkWrapper.body.createComponent, - } - : undefined - ); + const body = this.options.createComponent({ + id: options.id, + name: options.component, + }); - let header: IPaneHeaderPart; + let header: IPanePart | undefined; - if (options.headerComponent) { - header = createComponent( - options.id, - options.headerComponent, - this.options.headerComponents ?? {}, - this.options.headerframeworkComponents, - this.options.frameworkWrapper - ? { - createComponent: - this.options.frameworkWrapper.header - .createComponent, - } - : undefined - ); - } else { + if (options.headerComponent && this.options.createHeaderComponent) { + header = this.options.createHeaderComponent({ + id: options.id, + name: options.headerComponent, + }); + } + + if (!header) { header = new DefaultHeader(); } @@ -395,37 +372,24 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent { views: views.map((view) => { const data = view.data; - const body = createComponent( - data.id, - data.component, - this.options.components ?? {}, - this.options.frameworkComponents ?? {}, - this.options.frameworkWrapper - ? { - createComponent: - this.options.frameworkWrapper.body - .createComponent, - } - : undefined - ); + const body = this.options.createComponent({ + id: data.id, + name: data.component, + }); - let header: IPaneHeaderPart; + let header: IPanePart | undefined; - if (data.headerComponent) { - header = createComponent( - data.id, - data.headerComponent, - this.options.headerComponents ?? {}, - this.options.headerframeworkComponents ?? {}, - this.options.frameworkWrapper - ? { - createComponent: - this.options.frameworkWrapper.header - .createComponent, - } - : undefined - ); - } else { + if ( + data.headerComponent && + this.options.createHeaderComponent + ) { + header = this.options.createHeaderComponent({ + id: data.id, + name: data.headerComponent, + }); + } + + if (!header) { header = new DefaultHeader(); } @@ -483,9 +447,14 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent { } private doAddPanel(panel: PaneFramework): void { - const disposable = panel.onDidDrop((event) => { - this._onDidDrop.fire(event); - }); + const disposable = new CompositeDisposable( + panel.onDidDrop((event) => { + this._onDidDrop.fire(event); + }), + panel.onUnhandledDragOverEvent((event) => { + this._onUnhandledDragOverEvent.fire(event); + }) + ); this._viewDisposables.set(panel.id, disposable); } @@ -507,6 +476,8 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent { } this._viewDisposables.clear(); + this.element.remove(); + this.paneview.dispose(); } } diff --git a/packages/dockview-core/src/paneview/paneviewPanel.ts b/packages/dockview-core/src/paneview/paneviewPanel.ts index 38b4586f6..02710da90 100644 --- a/packages/dockview-core/src/paneview/paneviewPanel.ts +++ b/packages/dockview-core/src/paneview/paneviewPanel.ts @@ -36,13 +36,7 @@ export interface PanePanelComponentInitParameter api: PaneviewPanelApiImpl; } -export interface IPaneBodyPart extends IDisposable { - readonly element: HTMLElement; - update(params: PanelUpdateEvent): void; - init(parameters: PanePanelComponentInitParameter): void; -} - -export interface IPaneHeaderPart extends IDisposable { +export interface IPanePart extends IDisposable { readonly element: HTMLElement; update(params: PanelUpdateEvent): void; init(parameters: PanePanelComponentInitParameter): void; @@ -85,8 +79,8 @@ export abstract class PaneviewPanel private _isExpanded = false; protected header?: HTMLElement; protected body?: HTMLElement; - private bodyPart?: IPaneHeaderPart; - private headerPart?: IPaneBodyPart; + private bodyPart?: IPanePart; + private headerPart?: IPanePart; private expandedSize = 0; private animationTimer: any; private _orientation: Orientation; @@ -338,6 +332,6 @@ export abstract class PaneviewPanel }; } - protected abstract getBodyComponent(): IPaneBodyPart; - protected abstract getHeaderComponent(): IPaneHeaderPart; + protected abstract getBodyComponent(): IPanePart; + protected abstract getHeaderComponent(): IPanePart; } diff --git a/packages/dockview-core/src/scrollbar.scss b/packages/dockview-core/src/scrollbar.scss new file mode 100644 index 000000000..1276b51d8 --- /dev/null +++ b/packages/dockview-core/src/scrollbar.scss @@ -0,0 +1,28 @@ +.dv-scrollable { + position: relative; + overflow: hidden; + + .dv-scrollbar-horizontal { + position: absolute; + bottom: 0px; + left: 0px; + height: 4px; + border-radius: 2px; + background-color: transparent; + transition-property: background-color; + transition-timing-function: ease-in-out; + transition-duration: 1s; + transition-delay: 0s; + } + + &:hover, + &.dv-scrollable-resizing, + &.dv-scrollable-scrolling { + .dv-scrollbar-horizontal { + background-color: var( + --dv-scrollbar-background-color, + rgba(255, 255, 255, 0.25) + ); + } + } +} diff --git a/packages/dockview-core/src/scrollbar.ts b/packages/dockview-core/src/scrollbar.ts new file mode 100644 index 000000000..75db4e54a --- /dev/null +++ b/packages/dockview-core/src/scrollbar.ts @@ -0,0 +1,131 @@ +import { toggleClass, watchElementResize } from './dom'; +import { addDisposableListener } from './events'; +import { CompositeDisposable } from './lifecycle'; +import { clamp } from './math'; + +export class Scrollbar extends CompositeDisposable { + private readonly _element: HTMLElement; + private readonly _horizontalScrollbar: HTMLElement; + private _scrollLeft: number = 0; + private _animationTimer: any; + public static MouseWheelSpeed = 1; + + get element(): HTMLElement { + return this._element; + } + + constructor(private readonly scrollableElement: HTMLElement) { + super(); + + this._element = document.createElement('div'); + this._element.className = 'dv-scrollable'; + + this._horizontalScrollbar = document.createElement('div'); + this._horizontalScrollbar.className = 'dv-scrollbar-horizontal'; + + this.element.appendChild(scrollableElement); + this.element.appendChild(this._horizontalScrollbar); + + this.addDisposables( + addDisposableListener(this.element, 'wheel', (event) => { + this._scrollLeft += event.deltaY * Scrollbar.MouseWheelSpeed; + + this.calculateScrollbarStyles(); + }), + addDisposableListener( + this._horizontalScrollbar, + 'pointerdown', + (event) => { + event.preventDefault(); + + toggleClass(this.element, 'dv-scrollable-scrolling', true); + + const originalClientX = event.clientX; + const originalScrollLeft = this._scrollLeft; + + const onPointerMove = (event: PointerEvent) => { + const deltaX = event.clientX - originalClientX; + + const { clientWidth } = this.element; + const { scrollWidth } = this.scrollableElement; + const p = clientWidth / scrollWidth; + + this._scrollLeft = originalScrollLeft + deltaX / p; + this.calculateScrollbarStyles(); + }; + + const onEnd = () => { + toggleClass( + this.element, + 'dv-scrollable-scrolling', + false + ); + + document.removeEventListener( + 'pointermove', + onPointerMove + ); + document.removeEventListener('pointerup', onEnd); + document.removeEventListener('pointercancel', onEnd); + }; + + document.addEventListener('pointermove', onPointerMove); + document.addEventListener('pointerup', onEnd); + document.addEventListener('pointercancel', onEnd); + } + ), + addDisposableListener(this.element, 'scroll', () => { + this.calculateScrollbarStyles(); + }), + addDisposableListener(this.scrollableElement, 'scroll', () => { + this._scrollLeft = this.scrollableElement.scrollLeft; + this.calculateScrollbarStyles(); + }), + watchElementResize(this.element, () => { + toggleClass(this.element, 'dv-scrollable-resizing', true); + + if (this._animationTimer) { + clearTimeout(this._animationTimer); + } + + this._animationTimer = setTimeout(() => { + clearTimeout(this._animationTimer); + toggleClass(this.element, 'dv-scrollable-resizing', false); + }, 500); + + this.calculateScrollbarStyles(); + }) + ); + } + + private calculateScrollbarStyles(): void { + const { clientWidth } = this.element; + const { scrollWidth } = this.scrollableElement; + + const hasScrollbar = scrollWidth > clientWidth; + + if (hasScrollbar) { + const px = clientWidth * (clientWidth / scrollWidth); + this._horizontalScrollbar.style.width = `${px}px`; + + this._scrollLeft = clamp( + this._scrollLeft, + 0, + this.scrollableElement.scrollWidth - clientWidth + ); + + this.scrollableElement.scrollLeft = this._scrollLeft; + + const percentageComplete = + this._scrollLeft / (scrollWidth - clientWidth); + + this._horizontalScrollbar.style.left = `${ + (clientWidth - px) * percentageComplete + }px`; + } else { + this._horizontalScrollbar.style.width = `0px`; + this._horizontalScrollbar.style.left = `0px`; + this._scrollLeft = 0; + } + } +} diff --git a/packages/dockview-core/src/splitview/options.ts b/packages/dockview-core/src/splitview/options.ts index 01c32a040..05955f279 100644 --- a/packages/dockview-core/src/splitview/options.ts +++ b/packages/dockview-core/src/splitview/options.ts @@ -2,10 +2,7 @@ import { PanelInitParameters } from '../panel/types'; import { SplitViewOptions, LayoutPriority } from './splitview'; import { SplitviewPanel } from './splitviewPanel'; import { SplitviewComponent } from './splitviewComponent'; -import { - ComponentConstructor, - FrameworkFactory, -} from '../panel/componentFactory'; +import { CreateComponentOptions } from '../dockview/options'; export interface PanelViewInitParameters extends PanelInitParameters { minimumSize?: number; @@ -15,14 +12,32 @@ export interface PanelViewInitParameters extends PanelInitParameters { accessor: SplitviewComponent; } -export interface SplitviewComponentOptions extends SplitViewOptions { +export interface SplitviewOptions extends SplitViewOptions { disableAutoResizing?: boolean; - components?: { - [componentName: string]: ComponentConstructor; - }; - frameworkComponents?: { - [componentName: string]: any; - }; - frameworkWrapper?: FrameworkFactory; className?: string; } + +export interface SplitviewFrameworkOptions { + createComponent: (options: CreateComponentOptions) => SplitviewPanel; +} + +export type SplitviewComponentOptions = SplitviewOptions & + SplitviewFrameworkOptions; + +export const PROPERTY_KEYS_SPLITVIEW: (keyof SplitviewOptions)[] = (() => { + /** + * by readong the keys from an empty value object TypeScript will error + * when we add or remove new properties to `DockviewOptions` + */ + const properties: Record = { + orientation: undefined, + descriptor: undefined, + proportionalLayout: undefined, + styles: undefined, + margin: undefined, + disableAutoResizing: undefined, + className: undefined, + }; + + return Object.keys(properties) as (keyof SplitviewOptions)[]; +})(); diff --git a/packages/dockview-core/src/splitview/splitview.scss b/packages/dockview-core/src/splitview/splitview.scss index adf09b368..0e6ca303d 100644 --- a/packages/dockview-core/src/splitview/splitview.scss +++ b/packages/dockview-core/src/splitview/splitview.scss @@ -116,16 +116,18 @@ -moz-user-select: none; // Firefox -ms-user-select: none; // IE 10 and IE 11 touch-action: none; + background-color: var(--dv-sash-color, transparent); - &:not(.disabled):active { - transition: background-color 0.1s ease-in-out; - background-color: var(--dv-active-sash-color, transparent); - } - + &:not(.disabled):active, &:not(.disabled):hover { background-color: var(--dv-active-sash-color, transparent); - transition: background-color 0.1s ease-in-out; - transition-delay: 0.5s; + transition-property: background-color; + transition-timing-function: ease-in-out; + transition-duration: var( + --dv-active-sash-transition-duration, + 0.1s + ); + transition-delay: var(--dv-active-sash-transition-delay, 0.5s); } } } diff --git a/packages/dockview-core/src/splitview/splitview.ts b/packages/dockview-core/src/splitview/splitview.ts index c4ceeb3d0..c511d54f6 100644 --- a/packages/dockview-core/src/splitview/splitview.ts +++ b/packages/dockview-core/src/splitview/splitview.ts @@ -32,11 +32,11 @@ export interface ISplitviewStyles { } export interface SplitViewOptions { - readonly orientation: Orientation; - readonly descriptor?: ISplitViewDescriptor; - readonly proportionalLayout?: boolean; - readonly styles?: ISplitviewStyles; - readonly margin?: number; + orientation?: Orientation; + descriptor?: ISplitViewDescriptor; + proportionalLayout?: boolean; + styles?: ISplitviewStyles; + margin?: number; } export enum LayoutPriority { @@ -219,13 +219,15 @@ export class Splitview { set margin(value: number) { this._margin = value; + + toggleClass(this.element, 'dv-splitview-has-margin', value !== 0); } constructor( private readonly container: HTMLElement, options: SplitViewOptions ) { - this._orientation = options.orientation; + this._orientation = options.orientation ?? Orientation.VERTICAL; this.element = this.createContainer(); this.margin = options.margin ?? 0; diff --git a/packages/dockview-core/src/splitview/splitviewComponent.ts b/packages/dockview-core/src/splitview/splitviewComponent.ts index ab8bb6cf5..b97855f58 100644 --- a/packages/dockview-core/src/splitview/splitviewComponent.ts +++ b/packages/dockview-core/src/splitview/splitviewComponent.ts @@ -15,7 +15,6 @@ import { SplitviewComponentOptions } from './options'; import { BaseComponentOptions, Parameters } from '../panel/types'; import { Emitter, Event } from '../events'; import { SplitviewPanel, ISplitviewPanel } from './splitviewPanel'; -import { createComponent } from '../panel/componentFactory'; import { Resizable } from '../resizable'; import { Classnames } from '../dom'; @@ -156,23 +155,18 @@ export class SplitviewComponent : this.splitview.orthogonalSize; } - constructor( - parentElement: HTMLElement, - options: SplitviewComponentOptions - ) { - super(parentElement, options.disableAutoResizing); + constructor(container: HTMLElement, options: SplitviewComponentOptions) { + super(document.createElement('div'), options.disableAutoResizing); + this.element.style.height = '100%'; + this.element.style.width = '100%'; this._classNames = new Classnames(this.element); this._classNames.setClassNames(options.className ?? ''); - this._options = options; + // the container is owned by the third-party, do not modify/delete it + container.appendChild(this.element); - if (!options.components) { - options.components = {}; - } - if (!options.frameworkComponents) { - options.frameworkComponents = {}; - } + this._options = options; this.splitview = new Splitview(this.element, options); @@ -267,18 +261,10 @@ export class SplitviewComponent throw new Error(`panel ${options.id} already exists`); } - const view = createComponent( - options.id, - options.component, - this.options.components ?? {}, - this.options.frameworkComponents ?? {}, - this.options.frameworkWrapper - ? { - createComponent: - this.options.frameworkWrapper.createComponent, - } - : undefined - ); + const view = this.options.createComponent({ + id: options.id, + name: options.component, + }); view.orientation = this.splitview.orientation; @@ -367,19 +353,10 @@ export class SplitviewComponent throw new Error(`panel ${data.id} already exists`); } - const panel = createComponent( - data.id, - data.component, - this.options.components ?? {}, - this.options.frameworkComponents ?? {}, - this.options.frameworkWrapper - ? { - createComponent: - this.options.frameworkWrapper - .createComponent, - } - : undefined - ); + const panel = this.options.createComponent({ + id: data.id, + name: data.component, + }); queue.push(() => { panel.init({ @@ -448,6 +425,8 @@ export class SplitviewComponent view.dispose(); } + this.element.remove(); + super.dispose(); } } diff --git a/packages/dockview-core/src/theme.scss b/packages/dockview-core/src/theme.scss index 85aa66280..3b69d4c37 100644 --- a/packages/dockview-core/src/theme.scss +++ b/packages/dockview-core/src/theme.scss @@ -1,17 +1,31 @@ +@import 'theme/_sash-handle-mixin'; +@import 'theme/_drop-target-static-mixin'; +@import 'theme/_space-mixin'; + @mixin dockview-theme-core-mixin { --dv-paneview-active-outline-color: dodgerblue; --dv-tabs-and-actions-container-font-size: 13px; --dv-tabs-and-actions-container-height: 35px; --dv-drag-over-background-color: rgba(83, 89, 93, 0.5); - --dv-drag-over-border-color: white; + --dv-drag-over-border-color: transparent; --dv-tabs-container-scrollbar-color: #888; --dv-icon-hover-background-color: rgba(90, 93, 94, 0.31); --dv-floating-box-shadow: 8px 8px 8px 0px rgba(83, 89, 93, 0.5); --dv-overlay-z-index: 999; + // + + --dv-tab-font-size: inherit; + --dv-border-radius: 0px; + --dv-tab-margin: 0; + --dv-sash-color: transparent; + --dv-active-sash-color: transparent; + --dv-active-sash-transition-duration: 0.1s; + --dv-active-sash-transition-delay: 0.5s; } @mixin dockview-theme-dark-mixin { @include dockview-theme-core-mixin(); + @include dockview-drop-target-no-travel(); // --dv-group-view-background-color: #1e1e1e; @@ -35,6 +49,8 @@ @mixin dockview-theme-light-mixin { @include dockview-theme-core-mixin(); + @include dockview-drop-target-no-travel(); + // --dv-group-view-background-color: white; // @@ -53,6 +69,8 @@ // --dv-separator-border: rgba(128, 128, 128, 0.35); --dv-paneview-header-border-color: rgb(51, 51, 51); + + --dv-scrollbar-background-color: rgba(0, 0, 0, 0.25); } .dockview-theme-dark { @@ -131,30 +149,49 @@ @mixin dockview-theme-abyss-mixin { @include dockview-theme-core-mixin(); + @include dockview-drop-target-no-travel(); + + --dv-color-abyss-dark: #000c18; + --dv-color-abyss: #10192c; + --dv-color-abyss-light: #1c1c2a; + --dv-color-abyss-lighter: #2b2b4a; + --dv-color-abyss-accent: rgb(91, 30, 207); + + --dv-color-abyss-primary-text: white; + --dv-color-abyss-secondary-text: rgb(148, 151, 169); + // - --dv-group-view-background-color: #000c18; + --dv-group-view-background-color: var(--dv-color-abyss-dark); // - --dv-tabs-and-actions-container-background-color: #1c1c2a; + --dv-tabs-and-actions-container-background-color: var( + --dv-color-abyss-light + ); // - --dv-activegroup-visiblepanel-tab-background-color: #000c18; - --dv-activegroup-hiddenpanel-tab-background-color: #10192c; - --dv-inactivegroup-visiblepanel-tab-background-color: #000c18; - --dv-inactivegroup-hiddenpanel-tab-background-color: #10192c; - --dv-tab-divider-color: #2b2b4a; + --dv-activegroup-visiblepanel-tab-background-color: var( + --dv-color-abyss-dark + ); + --dv-activegroup-hiddenpanel-tab-background-color: var(--dv-color-abyss); + --dv-inactivegroup-visiblepanel-tab-background-color: var( + --dv-color-abyss-dark + ); + --dv-inactivegroup-hiddenpanel-tab-background-color: var(--dv-color-abyss); + --dv-tab-divider-color: var(--dv-color-abyss-lighter); // --dv-activegroup-visiblepanel-tab-color: white; --dv-activegroup-hiddenpanel-tab-color: rgba(255, 255, 255, 0.5); --dv-inactivegroup-visiblepanel-tab-color: rgba(255, 255, 255, 0.5); --dv-inactivegroup-hiddenpanel-tab-color: rgba(255, 255, 255, 0.25); // - --dv-separator-border: #2b2b4a; - --dv-paneview-header-border-color: #2b2b4a; + --dv-separator-border: var(--dv-color-abyss-lighter); + --dv-paneview-header-border-color: var(--dv-color-abyss-lighter); --dv-paneview-active-outline-color: #596f99; } @mixin dockview-theme-dracula-mixin { @include dockview-theme-core-mixin(); + @include dockview-drop-target-no-travel(); + // --dv-group-view-background-color: #282a36; // @@ -179,19 +216,21 @@ .dv-groupview { &.dv-active-group { > .dv-tabs-and-actions-container { - > .dv-tabs-container { - > .dv-tab.dv-active-tab { - position: relative; + > .dv-scrollable { + > .dv-tabs-container { + > .dv-tab.dv-active-tab { + position: relative; - &::after { - position: absolute; - left: 0px; - top: 0px; - content: ''; - width: 100%; - height: 1px; - background-color: #94527e; - z-index: 999; + &::after { + position: absolute; + left: 0px; + top: 0px; + content: ''; + width: 100%; + height: 1px; + background-color: #94527e; + z-index: 999; + } } } } @@ -199,19 +238,21 @@ } &.dv-inactive-group { > .dv-tabs-and-actions-container { - > .dv-tabs-container { - > .dv-tab.dv-active-tab { - position: relative; + > .dv-scrollable { + > .dv-tabs-container { + > .dv-tab.dv-active-tab { + position: relative; - &::after { - position: absolute; - left: 0px; - bottom: 0px; - content: ''; - width: 100%; - height: 1px; - background-color: #5e3d5a; - z-index: 999; + &::after { + position: absolute; + left: 0px; + bottom: 0px; + content: ''; + width: 100%; + height: 1px; + background-color: #5e3d5a; + z-index: 999; + } } } } @@ -229,10 +270,17 @@ } @mixin dockview-design-replit-mixin { + @include dockview-drop-target-no-travel(); + .dv-resize-container:has(> .dv-groupview) { border-radius: 8px; } + .dv-resize-container { + border-radius: 10px !important; + border: none; + } + .dv-groupview { overflow: hidden; border-radius: 10px; @@ -266,59 +314,16 @@ border: 1px solid transparent; } } - - .vertical > .sash-container > .sash { - &:not(.disabled) { - &::after { - content: ''; - height: 4px; - width: 40px; - border-radius: 2px; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background-color: var(--dv-separator-handle-background-color); - position: absolute; - } - - &:hover { - &::after { - background-color: var( - --dv-separator-handle-hover-background-color - ); - } - } - } - } - - .dv-horizontal > .dv-sash-container > .dv-sash { - &:not(.disabled) { - &::after { - content: ''; - height: 40px; - width: 4px; - border-radius: 2px; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - background-color: var(--dv-separator-handle-background-color); - position: absolute; - } - - &:hover { - &::after { - background-color: var( - --dv-separator-handle-hover-background-color - ); - } - } - } - } } .dockview-theme-replit { @include dockview-theme-core-mixin(); @include dockview-design-replit-mixin(); + @include dockview-design-handle-mixin(); + + padding: 10px; + background-color: #ebeced; + // --dv-group-view-background-color: #ebeced; // @@ -339,6 +344,117 @@ --dv-paneview-header-border-color: rgb(51, 51, 51); ///// - --dv-separator-handle-background-color: #cfd1d3; - --dv-separator-handle-hover-background-color: #babbbb; + --dv-sash-color: #cfd1d3; + --dv-active-sash-color: #babbbb; +} + +.dockview-theme-abyss-spaced { + @include dockview-theme-core-mixin(); + @include dockview-design-space-mixin(); + + // stylesheet + --dv-color-abyss-dark: rgb(11, 6, 17); + --dv-color-abyss: #16121f; + --dv-color-abyss-light: #201d2b; + --dv-color-abyss-lighter: #2a2837; + --dv-color-abyss-accent: rgb(91, 30, 207); + --dv-color-abyss-primary-text: white; + --dv-color-abyss-secondary-text: rgb(148, 151, 169); + + // + --dv-drag-over-border: 2px solid var(--dv-color-abyss-accent); + --dv-drag-over-background-color: ''; + // + + // + --dv-group-view-background-color: var(--dv-color-abyss-dark); + // + --dv-tabs-and-actions-container-background-color: var(--dv-color-abyss); + // + --dv-activegroup-visiblepanel-tab-background-color: var( + --dv-color-abyss-lighter + ); + --dv-activegroup-hiddenpanel-tab-background-color: var( + --dv-color-abyss-light + ); + --dv-inactivegroup-visiblepanel-tab-background-color: var( + --dv-color-abyss-lighter + ); + --dv-inactivegroup-hiddenpanel-tab-background-color: var( + --dv-color-abyss-light + ); + --dv-tab-divider-color: transparent; + // + --dv-activegroup-visiblepanel-tab-color: var(--dv-color-abyss-primary-text); + --dv-activegroup-hiddenpanel-tab-color: var( + --dv-color-abyss-secondary-text + ); + --dv-inactivegroup-visiblepanel-tab-color: var( + --dv-color-abyss-primary-text + ); + --dv-inactivegroup-hiddenpanel-tab-color: var( + --dv-color-abyss-secondary-text + ); + // + --dv-separator-border: transparent; + --dv-paneview-header-border-color: rgb(51, 51, 51); + + ///// + --dv-active-sash-color: var(--dv-color-abyss-accent); + // + --dv-floating-box-shadow: 8px 8px 8px 0px rgba(0, 0, 0, 0.5); + + padding: 10px; + background-color: var(--dv-color-abyss-dark); + + .dv-resize-container { + .dv-groupview { + border: 2px solid var(--dv-color-abyss-dark); + } + } +} + +.dockview-theme-light-spaced { + @include dockview-theme-core-mixin(); + @include dockview-design-space-mixin(); + + // + --dv-drag-over-border: 2px solid rgb(91, 30, 207); + --dv-drag-over-background-color: ''; + // + + // + --dv-group-view-background-color: #f6f5f9; + // + --dv-tabs-and-actions-container-background-color: white; + // + --dv-activegroup-visiblepanel-tab-background-color: #ededf0; + --dv-activegroup-hiddenpanel-tab-background-color: #f9f9fa; + --dv-inactivegroup-visiblepanel-tab-background-color: #ededf0; + --dv-inactivegroup-hiddenpanel-tab-background-color: #f9f9fa; + --dv-tab-divider-color: transparent; + // + --dv-activegroup-visiblepanel-tab-color: rgb(104, 107, 130); + --dv-activegroup-hiddenpanel-tab-color: rgb(148, 151, 169); + --dv-inactivegroup-visiblepanel-tab-color: rgb(104, 107, 130); + --dv-inactivegroup-hiddenpanel-tab-color: rgb(148, 151, 169); + // + --dv-separator-border: transparent; + --dv-paneview-header-border-color: rgb(51, 51, 51); + + ///// + --dv-active-sash-color: rgb(91, 30, 207); + // + --dv-floating-box-shadow: 8px 8px 8px 0px rgba(0, 0, 0, 0.1); + + padding: 10px; + background-color: #f6f5f9; + + --dv-scrollbar-background-color: rgba(0, 0, 0, 0.25); + + .dv-resize-container { + .dv-groupview { + border: 2px solid rgb(255, 255, 255, 0.1); + } + } } diff --git a/packages/dockview-core/src/theme/_drop-target-static-mixin.scss b/packages/dockview-core/src/theme/_drop-target-static-mixin.scss new file mode 100644 index 000000000..b2e0e4ba7 --- /dev/null +++ b/packages/dockview-core/src/theme/_drop-target-static-mixin.scss @@ -0,0 +1,10 @@ +@mixin dockview-drop-target-no-travel { + .dv-drop-target-container { + .dv-drop-target-anchor { + &.dv-drop-target-anchor-container-changed { + opacity: 0; + transition: none; + } + } + } +} diff --git a/packages/dockview-core/src/theme/_sash-handle-mixin.scss b/packages/dockview-core/src/theme/_sash-handle-mixin.scss new file mode 100644 index 000000000..31e5822da --- /dev/null +++ b/packages/dockview-core/src/theme/_sash-handle-mixin.scss @@ -0,0 +1,53 @@ +@mixin dockview-design-handle-mixin { + .dv-vertical > .dv-sash-container > .dv-sash { + background-color: transparent; + + &:not(.disabled) { + &::after { + content: ''; + height: 4px; + width: 40px; + border-radius: 2px; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: var(--dv-sash-color); + position: absolute; + } + + &:hover, + &:active { + background-color: transparent; + &::after { + background-color: var(--dv-active-sash-color); + } + } + } + } + + .dv-horizontal > .dv-sash-container > .dv-sash { + background-color: transparent; + + &:not(.disabled) { + &::after { + content: ''; + height: 40px; + width: 4px; + border-radius: 2px; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: var(--dv-sash-color); + position: absolute; + } + + &:hover, + &:active { + background-color: transparent; + &::after { + background-color: var(--dv-active-sash-color); + } + } + } + } +} diff --git a/packages/dockview-core/src/theme/_space-mixin.scss b/packages/dockview-core/src/theme/_space-mixin.scss new file mode 100644 index 000000000..2482b6841 --- /dev/null +++ b/packages/dockview-core/src/theme/_space-mixin.scss @@ -0,0 +1,57 @@ +@mixin dockview-design-space-mixin { + --dv-tab-font-size: 12px; + --dv-border-radius: 20px; + --dv-tab-margin: 0.5rem 0.25rem; + --dv-tabs-and-actions-container-height: 44px; + + --dv-border-radius: 20px; + + .dv-resize-container:has(> .dv-groupview) { + border-radius: 8px; + } + + .dv-sash { + border-radius: 4px; + } + + .dv-drop-target-anchor { + border-radius: calc(var(--dv-border-radius) / 4); + &.dv-drop-target-content { + border-radius: var(--dv-border-radius); + } + } + + .dv-resize-container { + border-radius: var(--dv-border-radius) !important; + border: none; + } + + .dv-tabs-overflow-container, + .dv-tabs-overflow-dropdown-default { + border-radius: 8px; + height: unset !important; + } + + .dv-tab { + border-radius: 8px; + + .dv-svg { + height: 8px; + width: 8px; + } + } + + .dv-groupview { + border-radius: var(--dv-border-radius); + + .dv-tabs-and-actions-container { + padding: 0px calc(var(--dv-border-radius) / 2); + } + + .dv-content-container { + background-color: var( + --dv-tabs-and-actions-container-background-color + ); + } + } +} diff --git a/packages/dockview-react/package.json b/packages/dockview-react/package.json index c0051e41f..6d5fa2b75 100644 --- a/packages/dockview-react/package.json +++ b/packages/dockview-react/package.json @@ -1,6 +1,6 @@ { "name": "dockview-react", - "version": "2.0.0", + "version": "4.0.0", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,6 +54,6 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-react --coverage" }, "dependencies": { - "dockview": "^2.0.0" + "dockview": "^4.0.0" } } diff --git a/packages/dockview-vue/package.json b/packages/dockview-vue/package.json index d5f23fd8a..dfa4dedaf 100644 --- a/packages/dockview-vue/package.json +++ b/packages/dockview-vue/package.json @@ -1,6 +1,6 @@ { "name": "dockview-vue", - "version": "2.0.0", + "version": "4.0.0", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -52,6 +52,9 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-vue --coverage" }, "dependencies": { - "dockview-core": "^2.0.0" + "dockview-core": "^4.0.0" + }, + "peerDependencies": { + "vue": "^3.4.0" } } diff --git a/packages/dockview-vue/src/dockview/dockview.vue b/packages/dockview-vue/src/dockview/dockview.vue index 6442ad5eb..35508d49a 100644 --- a/packages/dockview-vue/src/dockview/dockview.vue +++ b/packages/dockview-vue/src/dockview/dockview.vue @@ -2,7 +2,7 @@ import { DockviewApi, type DockviewOptions, - PROPERTY_KEYS, + PROPERTY_KEYS_DOCKVIEW, type DockviewFrameworkOptions, createDockview, } from 'dockview-core'; @@ -25,7 +25,7 @@ import { import type { IDockviewVueProps, VueEvents } from './types'; function extractCoreOptions(props: IDockviewVueProps): DockviewOptions { - const coreOptions = (PROPERTY_KEYS as (keyof DockviewOptions)[]).reduce( + const coreOptions = (PROPERTY_KEYS_DOCKVIEW as (keyof DockviewOptions)[]).reduce( (obj, key) => { (obj as any)[key] = props[key]; return obj; @@ -43,7 +43,7 @@ const props = defineProps(); const el = ref(null); const instance = ref(null); -PROPERTY_KEYS.forEach((coreOptionKey) => { +PROPERTY_KEYS_DOCKVIEW.forEach((coreOptionKey) => { watch( () => props[coreOptionKey], (newValue, oldValue) => { diff --git a/packages/dockview-vue/src/utils.ts b/packages/dockview-vue/src/utils.ts index 45c23fafd..3b4c4fa71 100644 --- a/packages/dockview-vue/src/utils.ts +++ b/packages/dockview-vue/src/utils.ts @@ -2,7 +2,6 @@ import type { DockviewApi, DockviewGroupPanel, DockviewPanelApi, - GroupPanelPartInitParameters, IContentRenderer, IDockviewPanelHeaderProps, IGroupHeaderProps, @@ -12,6 +11,7 @@ import type { IWatermarkRenderer, PanelUpdateEvent, Parameters, + TabPartInitParameters, WatermarkRendererInitParameters, } from 'dockview-core'; import { @@ -121,7 +121,7 @@ export class VueRenderer private _api: DockviewPanelApi | undefined; private _containerApi: DockviewApi | undefined; - init(parameters: GroupPanelPartInitParameters): void { + init(parameters: TabPartInitParameters): void { this._api = parameters.api; this._containerApi = parameters.containerApi; @@ -129,6 +129,7 @@ export class VueRenderer params: parameters.params, api: parameters.api, containerApi: parameters.containerApi, + tabLocation: parameters.tabLocation, }; this._renderDisposable?.dispose(); diff --git a/packages/dockview/package.json b/packages/dockview/package.json index a26e233c4..51b0dd5b9 100644 --- a/packages/dockview/package.json +++ b/packages/dockview/package.json @@ -1,6 +1,6 @@ { "name": "dockview", - "version": "2.0.0", + "version": "4.0.0", "description": "Zero dependency layout manager supporting tabs, grids and splitviews", "keywords": [ "splitview", @@ -54,7 +54,7 @@ "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage" }, "dependencies": { - "dockview-core": "^2.0.0" + "dockview-core": "^4.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" diff --git a/packages/dockview/src/__tests__/dockview/defaultTab.spec.tsx b/packages/dockview/src/__tests__/dockview/defaultTab.spec.tsx index 427e9650e..85a8fe0ce 100644 --- a/packages/dockview/src/__tests__/dockview/defaultTab.spec.tsx +++ b/packages/dockview/src/__tests__/dockview/defaultTab.spec.tsx @@ -10,13 +10,16 @@ import { Disposable } from 'dockview-core/dist/cjs/lifecycle'; describe('defaultTab', () => { test('has close button by default', async () => { const api = fromPartial({ - onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE), + onDidTitleChange: jest + .fn() + .mockImplementation(() => Disposable.NONE), }); const containerApi = fromPartial({}); const params = {}; render( { test('that title is displayed', async () => { const api = fromPartial({ title: 'test_title', - onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE), + onDidTitleChange: jest + .fn() + .mockImplementation(() => Disposable.NONE), }); const containerApi = fromPartial({}); const params = {}; render( { render( { test('has no close button when hideClose=true', async () => { const api = fromPartial({ - onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE), + onDidTitleChange: jest + .fn() + .mockImplementation(() => Disposable.NONE), }); const containerApi = fromPartial({}); const params = {}; render( { test('that settings closeActionOverride skips api.close()', async () => { const api = fromPartial({ close: jest.fn(), - onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE), + onDidTitleChange: jest + .fn() + .mockImplementation(() => Disposable.NONE), }); const containerApi = fromPartial({}); const params = {}; @@ -114,6 +126,7 @@ describe('defaultTab', () => { render( { test('that clicking close calls api.close()', async () => { const api = fromPartial({ close: jest.fn(), - onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE), + onDidTitleChange: jest + .fn() + .mockImplementation(() => Disposable.NONE), }); const containerApi = fromPartial({}); const params = {}; render( { test('has close button when hideClose=false', async () => { const api = fromPartial({ - onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE), + onDidTitleChange: jest + .fn() + .mockImplementation(() => Disposable.NONE), }); const containerApi = fromPartial({}); const params = {}; render( { const element = await screen.getByTestId('dockview-dv-default-tab'); expect(element.querySelector('.dv-default-tab-action')).toBeTruthy(); }); - - test('that pointerDown on close button prevents panel becoming active', async () => { - const api = fromPartial({ - setActive: jest.fn(), - onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE), - }); - const containerApi = fromPartial({}); - const params = {}; - - render( - - ); - - const element = await screen.getByTestId('dockview-dv-default-tab'); - const btn = element.querySelector( - '.dv-default-tab-action' - ) as HTMLElement; - - fireEvent.pointerDown(btn); - expect(api.setActive).toHaveBeenCalledTimes(0); - - fireEvent.click(element); - expect(api.setActive).toHaveBeenCalledTimes(1); - }); }); diff --git a/packages/dockview/src/__tests__/dockview/headerActionsRenderer.spec.ts b/packages/dockview/src/__tests__/dockview/headerActionsRenderer.spec.ts index ec6015403..be273602f 100644 --- a/packages/dockview/src/__tests__/dockview/headerActionsRenderer.spec.ts +++ b/packages/dockview/src/__tests__/dockview/headerActionsRenderer.spec.ts @@ -37,7 +37,7 @@ describe('headerActionsRenderer', () => { ); expect(cut.element.childNodes.length).toBe(0); - expect(cut.element.className).toBe('dockview-react-part'); + expect(cut.element.className).toBe('dv-react-part'); expect(cut.part).toBeUndefined(); cut.init({ diff --git a/packages/dockview/src/dockview/defaultTab.tsx b/packages/dockview/src/dockview/defaultTab.tsx index f1586fa83..f8164560f 100644 --- a/packages/dockview/src/dockview/defaultTab.tsx +++ b/packages/dockview/src/dockview/defaultTab.tsx @@ -32,10 +32,16 @@ export const DockviewDefaultTab: React.FunctionComponent< params: _params, hideClose, closeActionOverride, + onPointerDown, + onPointerUp, + onPointerLeave, + tabLocation, ...rest }) => { const title = useTitle(api); + const isMiddleMouseButton = React.useRef(false); + const onClose = React.useCallback( (event: React.MouseEvent) => { event.preventDefault(); @@ -49,37 +55,52 @@ export const DockviewDefaultTab: React.FunctionComponent< [api, closeActionOverride] ); - const onPointerDown = React.useCallback((e: React.MouseEvent) => { - e.preventDefault(); + const onBtnPointerDown = React.useCallback((event: React.MouseEvent) => { + event.preventDefault(); }, []); - const onClick = React.useCallback( - (event: React.MouseEvent) => { - if (event.defaultPrevented) { - return; - } - - api.setActive(); - - if (rest.onClick) { - rest.onClick(event); - } + const _onPointerDown = React.useCallback( + (event: React.PointerEvent) => { + isMiddleMouseButton.current = event.button === 1; + onPointerDown?.(event); }, - [api, rest.onClick] + [onPointerDown] + ); + + const _onPointerUp = React.useCallback( + (event: React.PointerEvent) => { + if (isMiddleMouseButton && event.button === 1 && !hideClose) { + isMiddleMouseButton.current = false; + onClose(event); + } + + onPointerUp?.(event); + }, + [onPointerUp, onClose, hideClose] + ); + + const _onPointerLeave = React.useCallback( + (event: React.PointerEvent) => { + isMiddleMouseButton.current = false; + onPointerLeave?.(event); + }, + [onPointerLeave] ); return (
{title} - {!hideClose && ( + {!hideClose && tabLocation !== 'headerOverflow' && (
diff --git a/packages/dockview/src/dockview/dockview.tsx b/packages/dockview/src/dockview/dockview.tsx index 550c632b9..2e4e2fc57 100644 --- a/packages/dockview/src/dockview/dockview.tsx +++ b/packages/dockview/src/dockview/dockview.tsx @@ -10,7 +10,7 @@ import { IDockviewPanelHeaderProps, IDockviewPanelProps, DockviewOptions, - PROPERTY_KEYS, + PROPERTY_KEYS_DOCKVIEW, DockviewComponentOptions, DockviewFrameworkOptions, DockviewReadyEvent, @@ -40,7 +40,6 @@ function createGroupControlElement( const DEFAULT_REACT_TAB = 'props.defaultTabComponent'; export interface IDockviewReactProps extends DockviewOptions { - className?: string; tabComponents?: Record< string, React.FunctionComponent @@ -58,7 +57,7 @@ export interface IDockviewReactProps extends DockviewOptions { } function extractCoreOptions(props: IDockviewReactProps): DockviewOptions { - const coreOptions = PROPERTY_KEYS.reduce((obj, key) => { + const coreOptions = PROPERTY_KEYS_DOCKVIEW.reduce((obj, key) => { if (key in props) { obj[key] = props[key] as any; } @@ -82,7 +81,7 @@ export const DockviewReact = React.forwardRef( () => { const changes: Partial = {}; - PROPERTY_KEYS.forEach((propKey) => { + PROPERTY_KEYS_DOCKVIEW.forEach((propKey) => { const key = propKey; const propValue = props[key]; @@ -99,7 +98,7 @@ export const DockviewReact = React.forwardRef( prevProps.current = props; }, - PROPERTY_KEYS.map((key) => props[key]) + PROPERTY_KEYS_DOCKVIEW.map((key) => props[key]) ); React.useEffect(() => { @@ -319,11 +318,7 @@ export const DockviewReact = React.forwardRef( }, [props.prefixHeaderActionsComponent]); return ( -
+
{portals}
); diff --git a/packages/dockview/src/dockview/headerActionsRenderer.ts b/packages/dockview/src/dockview/headerActionsRenderer.ts index c856b1bbc..fcf4f273d 100644 --- a/packages/dockview/src/dockview/headerActionsRenderer.ts +++ b/packages/dockview/src/dockview/headerActionsRenderer.ts @@ -30,7 +30,7 @@ export class ReactHeaderActionsRendererPart implements IHeaderActionsRenderer { private readonly _group: DockviewGroupPanel ) { this._element = document.createElement('div'); - this._element.className = 'dockview-react-part'; + this._element.className = 'dv-react-part'; this._element.style.height = '100%'; this._element.style.width = '100%'; } diff --git a/packages/dockview/src/dockview/reactContentPart.ts b/packages/dockview/src/dockview/reactContentPart.ts index 46d8b3bb4..984a911cb 100644 --- a/packages/dockview/src/dockview/reactContentPart.ts +++ b/packages/dockview/src/dockview/reactContentPart.ts @@ -29,7 +29,7 @@ export class ReactPanelContentPart implements IContentRenderer { private readonly reactPortalStore: ReactPortalStore ) { this._element = document.createElement('div'); - this._element.className = 'dockview-react-part'; + this._element.className = 'dv-react-part'; this._element.style.height = '100%'; this._element.style.width = '100%'; } diff --git a/packages/dockview/src/dockview/reactHeaderPart.ts b/packages/dockview/src/dockview/reactHeaderPart.ts index 772a4a814..b3f19e9af 100644 --- a/packages/dockview/src/dockview/reactHeaderPart.ts +++ b/packages/dockview/src/dockview/reactHeaderPart.ts @@ -3,13 +3,13 @@ import { ReactPart, ReactPortalStore } from '../react'; import { PanelUpdateEvent, ITabRenderer, - GroupPanelPartInitParameters, - IGroupPanelBaseProps, + TabPartInitParameters, + IDockviewPanelHeaderProps, } from 'dockview-core'; export class ReactPanelHeaderPart implements ITabRenderer { private readonly _element: HTMLElement; - private part?: ReactPart; + private part?: ReactPart; get element(): HTMLElement { return this._element; @@ -17,11 +17,11 @@ export class ReactPanelHeaderPart implements ITabRenderer { constructor( public readonly id: string, - private readonly component: React.FunctionComponent, + private readonly component: React.FunctionComponent, private readonly reactPortalStore: ReactPortalStore ) { this._element = document.createElement('div'); - this._element.className = 'dockview-react-part'; + this._element.className = 'dv-react-part'; this._element.style.height = '100%'; this._element.style.width = '100%'; } @@ -30,7 +30,7 @@ export class ReactPanelHeaderPart implements ITabRenderer { //noop } - public init(parameters: GroupPanelPartInitParameters): void { + public init(parameters: TabPartInitParameters): void { this.part = new ReactPart( this.element, this.reactPortalStore, @@ -39,6 +39,7 @@ export class ReactPanelHeaderPart implements ITabRenderer { params: parameters.params, api: parameters.api, containerApi: parameters.containerApi, + tabLocation: parameters.tabLocation, } ); } diff --git a/packages/dockview/src/dockview/reactWatermarkPart.ts b/packages/dockview/src/dockview/reactWatermarkPart.ts index f4b95c02e..20fdf3f39 100644 --- a/packages/dockview/src/dockview/reactWatermarkPart.ts +++ b/packages/dockview/src/dockview/reactWatermarkPart.ts @@ -23,7 +23,7 @@ export class ReactWatermarkPart implements IWatermarkRenderer { private readonly reactPortalStore: ReactPortalStore ) { this._element = document.createElement('div'); - this._element.className = 'dockview-react-part'; + this._element.className = 'dv-react-part'; this._element.style.height = '100%'; this._element.style.width = '100%'; } diff --git a/packages/dockview/src/gridview/gridview.tsx b/packages/dockview/src/gridview/gridview.tsx index c63307593..9bc9dd60b 100644 --- a/packages/dockview/src/gridview/gridview.tsx +++ b/packages/dockview/src/gridview/gridview.tsx @@ -1,13 +1,17 @@ import React from 'react'; import { GridviewPanelApi, - Orientation, GridviewApi, createGridview, + GridviewOptions, + PROPERTY_KEYS_GRIDVIEW, + GridviewComponentOptions, + GridviewFrameworkOptions, } from 'dockview-core'; import { ReactGridPanelView } from './view'; import { usePortalsLifecycle } from '../react'; import { PanelParameters } from '../types'; + export interface GridviewReadyEvent { api: GridviewApi; } @@ -18,14 +22,20 @@ export interface IGridviewPanelProps containerApi: GridviewApi; } -export interface IGridviewReactProps { - orientation?: Orientation; +export interface IGridviewReactProps extends GridviewOptions { onReady: (event: GridviewReadyEvent) => void; components: Record>; - hideBorders?: boolean; - className?: string; - proportionalLayout?: boolean; - disableAutoResizing?: boolean; +} + +function extractCoreOptions(props: IGridviewReactProps): GridviewOptions { + const coreOptions = PROPERTY_KEYS_GRIDVIEW.reduce((obj, key) => { + if (key in props) { + obj[key] = props[key] as any; + } + return obj; + }, {} as Partial); + + return coreOptions as GridviewOptions; } export const GridviewReact = React.forwardRef( @@ -36,6 +46,32 @@ export const GridviewReact = React.forwardRef( React.useImperativeHandle(ref, () => domRef.current!, []); + const prevProps = React.useRef>({}); + + React.useEffect( + () => { + const changes: Partial = {}; + + PROPERTY_KEYS_GRIDVIEW.forEach((propKey) => { + const key = propKey; + const propValue = props[key]; + + if (key in props && propValue !== prevProps.current[key]) { + changes[key] = propValue as any; + } + }); + + if (gridviewRef.current) { + gridviewRef.current.updateOptions(changes); + } else { + // not yet fully initialized + } + + prevProps.current = props; + }, + PROPERTY_KEYS_GRIDVIEW.map((key) => props[key]) + ); + React.useEffect(() => { if (!domRef.current) { return () => { @@ -43,29 +79,20 @@ export const GridviewReact = React.forwardRef( }; } - const api = createGridview(domRef.current, { - disableAutoResizing: props.disableAutoResizing, - proportionalLayout: - typeof props.proportionalLayout === 'boolean' - ? props.proportionalLayout - : true, - orientation: props.orientation ?? Orientation.HORIZONTAL, - frameworkComponents: props.components, - frameworkComponentFactory: { - createComponent: (id: string, componentId, component) => { - return new ReactGridPanelView( - id, - componentId, - component, - { - addPortal, - } - ); - }, + const frameworkOptions: GridviewFrameworkOptions = { + createComponent: (options) => { + return new ReactGridPanelView( + options.id, + options.name, + props.components[options.name], + { addPortal } + ); }, - styles: props.hideBorders - ? { separatorBorder: 'transparent' } - : undefined, + }; + + const api = createGridview(domRef.current, { + ...extractCoreOptions(props), + ...frameworkOptions, }); const { clientWidth, clientHeight } = domRef.current; @@ -87,16 +114,19 @@ export const GridviewReact = React.forwardRef( return; } gridviewRef.current.updateOptions({ - frameworkComponents: props.components, + createComponent: (options) => { + return new ReactGridPanelView( + options.id, + options.name, + props.components[options.name], + { addPortal } + ); + }, }); }, [props.components]); return ( -
+
{portals}
); diff --git a/packages/dockview/src/paneview/paneview.tsx b/packages/dockview/src/paneview/paneview.tsx index b5db59540..e0d49d98d 100644 --- a/packages/dockview/src/paneview/paneview.tsx +++ b/packages/dockview/src/paneview/paneview.tsx @@ -1,10 +1,13 @@ import React from 'react'; import { PaneviewPanelApi, - PaneviewDndOverlayEvent, PaneviewApi, PaneviewDropEvent, createPaneview, + PaneviewOptions, + PROPERTY_KEYS_PANEVIEW, + PaneviewComponentOptions, + PaneviewFrameworkOptions, } from 'dockview-core'; import { usePortalsLifecycle } from '../react'; import { PanePanelSection } from './view'; @@ -21,20 +24,27 @@ export interface IPaneviewPanelProps title: string; } -export interface IPaneviewReactProps { +export interface IPaneviewReactProps extends PaneviewOptions { onReady: (event: PaneviewReadyEvent) => void; components: Record>; headerComponents?: Record< string, React.FunctionComponent >; - className?: string; - disableAutoResizing?: boolean; - disableDnd?: boolean; - showDndOverlay?: (event: PaneviewDndOverlayEvent) => boolean; onDidDrop?(event: PaneviewDropEvent): void; } +function extractCoreOptions(props: IPaneviewReactProps): PaneviewOptions { + const coreOptions = PROPERTY_KEYS_PANEVIEW.reduce((obj, key) => { + if (key in props) { + obj[key] = props[key] as any; + } + return obj; + }, {} as Partial); + + return coreOptions as PaneviewOptions; +} + export const PaneviewReact = React.forwardRef( (props: IPaneviewReactProps, ref: React.ForwardedRef) => { const domRef = React.useRef(null); @@ -43,35 +53,64 @@ export const PaneviewReact = React.forwardRef( React.useImperativeHandle(ref, () => domRef.current!, []); - React.useEffect(() => { - const createComponent = ( - id: string, - _componentId: string, - component: any - ) => - new PanePanelSection(id, component, { - addPortal, + const prevProps = React.useRef>({}); + + React.useEffect( + () => { + const changes: Partial = {}; + + PROPERTY_KEYS_PANEVIEW.forEach((propKey) => { + const key = propKey; + const propValue = props[key]; + + if (key in props && propValue !== prevProps.current[key]) { + changes[key] = propValue as any; + } }); - const api = createPaneview(domRef.current!, { - disableAutoResizing: props.disableAutoResizing, - frameworkComponents: props.components, - components: {}, - headerComponents: {}, - disableDnd: props.disableDnd, - headerframeworkComponents: props.headerComponents, - frameworkWrapper: { - header: { - createComponent, - }, - body: { - createComponent, - }, + if (paneviewRef.current) { + paneviewRef.current.updateOptions(changes); + } else { + // not yet fully initialized + } + + prevProps.current = props; + }, + PROPERTY_KEYS_PANEVIEW.map((key) => props[key]) + ); + + React.useEffect(() => { + if (!domRef.current) { + return () => { + // noop + }; + } + + const headerComponents = props.headerComponents ?? {}; + + const frameworkOptions: PaneviewFrameworkOptions = { + createComponent: (options) => { + return new PanePanelSection( + options.id, + props.components[options.name], + { addPortal } + ); }, - showDndOverlay: props.showDndOverlay, + createHeaderComponent: (options) => { + return new PanePanelSection( + options.id, + headerComponents[options.name], + { addPortal } + ); + }, + }; + + const api = createPaneview(domRef.current, { + ...extractCoreOptions(props), + ...frameworkOptions, }); - const { clientWidth, clientHeight } = domRef.current!; + const { clientWidth, clientHeight } = domRef.current; api.layout(clientWidth, clientHeight); if (props.onReady) { @@ -90,7 +129,13 @@ export const PaneviewReact = React.forwardRef( return; } paneviewRef.current.updateOptions({ - frameworkComponents: props.components, + createComponent: (options) => { + return new PanePanelSection( + options.id, + props.components[options.name], + { addPortal } + ); + }, }); }, [props.components]); @@ -98,26 +143,30 @@ export const PaneviewReact = React.forwardRef( if (!paneviewRef.current) { return; } + + const headerComponents = props.headerComponents ?? {}; + paneviewRef.current.updateOptions({ - headerframeworkComponents: props.headerComponents, + createHeaderComponent: (options) => { + return new PanePanelSection( + options.id, + headerComponents[options.name], + { addPortal } + ); + }, }); }, [props.headerComponents]); React.useEffect(() => { if (!paneviewRef.current) { return () => { - // + // noop }; } - const api = paneviewRef.current; - - const disposable = api.onDidDrop((event) => { + const disposable = paneviewRef.current.onDidDrop((event) => { if (props.onDidDrop) { - props.onDidDrop({ - ...event, - api, - }); + props.onDidDrop(event); } }); @@ -126,21 +175,8 @@ export const PaneviewReact = React.forwardRef( }; }, [props.onDidDrop]); - React.useEffect(() => { - if (!paneviewRef.current) { - return; - } - paneviewRef.current.updateOptions({ - showDndOverlay: props.showDndOverlay, - }); - }, [props.showDndOverlay]); - return ( -
+
{portals}
); diff --git a/packages/dockview/src/paneview/view.ts b/packages/dockview/src/paneview/view.ts index 3d3a5d8f6..6840e49d4 100644 --- a/packages/dockview/src/paneview/view.ts +++ b/packages/dockview/src/paneview/view.ts @@ -1,13 +1,13 @@ import React from 'react'; import { PanelUpdateEvent, - IPaneBodyPart, + IPanePart, PanePanelComponentInitParameter, } from 'dockview-core'; import { ReactPart, ReactPortalStore } from '../react'; import { IPaneviewPanelProps } from './paneview'; -export class PanePanelSection implements IPaneBodyPart { +export class PanePanelSection implements IPanePart { private readonly _element: HTMLElement; private part?: ReactPart; diff --git a/packages/dockview/src/splitview/splitview.tsx b/packages/dockview/src/splitview/splitview.tsx index 6c516ce8c..bb8593381 100644 --- a/packages/dockview/src/splitview/splitview.tsx +++ b/packages/dockview/src/splitview/splitview.tsx @@ -2,8 +2,11 @@ import React from 'react'; import { SplitviewApi, SplitviewPanelApi, - Orientation, createSplitview, + SplitviewOptions, + PROPERTY_KEYS_SPLITVIEW, + SplitviewFrameworkOptions, + SplitviewComponentOptions, } from 'dockview-core'; import { usePortalsLifecycle } from '../react'; import { PanelParameters } from '../types'; @@ -19,14 +22,20 @@ export interface ISplitviewPanelProps containerApi: SplitviewApi; } -export interface ISplitviewReactProps { - orientation?: Orientation; +export interface ISplitviewReactProps extends SplitviewOptions { onReady: (event: SplitviewReadyEvent) => void; components: Record>; - proportionalLayout?: boolean; - hideBorders?: boolean; - className?: string; - disableAutoResizing?: boolean; +} + +function extractCoreOptions(props: ISplitviewReactProps): SplitviewOptions { + const coreOptions = PROPERTY_KEYS_SPLITVIEW.reduce((obj, key) => { + if (key in props) { + obj[key] = props[key] as any; + } + return obj; + }, {} as Partial); + + return coreOptions as SplitviewOptions; } export const SplitviewReact = React.forwardRef( @@ -37,32 +46,56 @@ export const SplitviewReact = React.forwardRef( React.useImperativeHandle(ref, () => domRef.current!, []); + const prevProps = React.useRef>({}); + + React.useEffect( + () => { + const changes: Partial = {}; + + PROPERTY_KEYS_SPLITVIEW.forEach((propKey) => { + const key = propKey; + const propValue = props[key]; + + if (key in props && propValue !== prevProps.current[key]) { + changes[key] = propValue as any; + } + }); + + if (splitviewRef.current) { + splitviewRef.current.updateOptions(changes); + } else { + // not yet fully initialized + } + + prevProps.current = props; + }, + PROPERTY_KEYS_SPLITVIEW.map((key) => props[key]) + ); + React.useEffect(() => { - const api = createSplitview(domRef.current!, { - disableAutoResizing: props.disableAutoResizing, - orientation: props.orientation ?? Orientation.HORIZONTAL, - frameworkComponents: props.components, - frameworkWrapper: { - createComponent: ( - id: string, - componentId, - component: any - ) => { - return new ReactPanelView(id, componentId, component, { - addPortal, - }); - }, + if (!domRef.current) { + return () => { + // noop + }; + } + + const frameworkOptions: SplitviewFrameworkOptions = { + createComponent: (options) => { + return new ReactPanelView( + options.id, + options.name, + props.components[options.name], + { addPortal } + ); }, - proportionalLayout: - typeof props.proportionalLayout === 'boolean' - ? props.proportionalLayout - : true, - styles: props.hideBorders - ? { separatorBorder: 'transparent' } - : undefined, + }; + + const api = createSplitview(domRef.current, { + ...extractCoreOptions(props), + ...frameworkOptions, }); - const { clientWidth, clientHeight } = domRef.current!; + const { clientWidth, clientHeight } = domRef.current; api.layout(clientWidth, clientHeight); if (props.onReady) { @@ -81,16 +114,19 @@ export const SplitviewReact = React.forwardRef( return; } splitviewRef.current.updateOptions({ - frameworkComponents: props.components, + createComponent: (options) => { + return new ReactPanelView( + options.id, + options.name, + props.components[options.name], + { addPortal } + ); + }, }); }, [props.components]); return ( -
+
{portals}
); diff --git a/packages/dockview/src/svg.tsx b/packages/dockview/src/svg.tsx index 76143411a..eccf52bb6 100644 --- a/packages/dockview/src/svg.tsx +++ b/packages/dockview/src/svg.tsx @@ -7,7 +7,7 @@ export const CloseButton = () => ( viewBox="0 0 28 28" aria-hidden={'false'} focusable={false} - className="dockview-svg" + className="dv-svg" > @@ -21,7 +21,7 @@ export const ExpandMore = () => { viewBox="0 0 24 15" aria-hidden={'false'} focusable={false} - className="dockview-svg" + className="dv-svg" > diff --git a/packages/docs/blog/2024-12-17-dockview-2.1.0.md b/packages/docs/blog/2024-12-17-dockview-2.1.0.md new file mode 100644 index 000000000..7d58a65d3 --- /dev/null +++ b/packages/docs/blog/2024-12-17-dockview-2.1.0.md @@ -0,0 +1,28 @@ +--- +slug: dockview-2.1.0-release +title: Dockview 2.1.0 +tags: [release] +--- + +# Release Notes + +Please reference docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features + +- Persist custom popout urls in layouts [#769](https://github.com/mathuo/dockview/pull/769) +- Ensure group always exists [#783](https://github.com/mathuo/dockview/pull/783) +- Serialization of maximized views [#762](https://github.com/mathuo/dockview/pull/762) +- Set `react` as an explicit peerDependency of the `dockview` package +- Make tabs container non-focusable [#761](https://github.com/mathuo/dockview/pull/761) + +## 🛠 Miscs + +- Bug: fix `setVisible` for floating groups [#755](https://github.com/mathuo/dockview/pull/755) +- Bug: fix `onDidAddGroup` event firing when adding floating groups and panels [#785](https://github.com/mathuo/dockview/pull/785) +- Documentation [#743](https://github.com/mathuo/dockview/pull/743) [#770](https://github.com/mathuo/dockview/pull/770) +- Build tooling [#789](https://github.com/mathuo/dockview/pull/789) [#759](https://github.com/mathuo/dockview/pull/759) + +## 🔥 Breaking changes + + diff --git a/packages/docs/blog/2024-12-20-dockview-2.1.1.md b/packages/docs/blog/2024-12-20-dockview-2.1.1.md new file mode 100644 index 000000000..7594a64ee --- /dev/null +++ b/packages/docs/blog/2024-12-20-dockview-2.1.1.md @@ -0,0 +1,19 @@ +--- +slug: dockview-2.1.1-release +title: Dockview 2.1.1 +tags: [release] +--- + +# Release Notes + +Please reference docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features + +## 🛠 Miscs + +- Bug: Fix issue with moving tab from popout group into main grid [#795](https://github.com/mathuo/dockview/issues/795) + +## 🔥 Breaking changes + + diff --git a/packages/docs/blog/2024-12-21-dockview-2.1.2.md b/packages/docs/blog/2024-12-21-dockview-2.1.2.md new file mode 100644 index 000000000..cc27b44b2 --- /dev/null +++ b/packages/docs/blog/2024-12-21-dockview-2.1.2.md @@ -0,0 +1,19 @@ +--- +slug: dockview-2.1.2-release +title: Dockview 2.1.2 +tags: [release] +--- + +# Release Notes + +Please reference docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features + +## 🛠 Miscs + +- Bug: Fix issues with popout group location transitions [#797](https://github.com/mathuo/dockview/issues/797) + +## 🔥 Breaking changes + + diff --git a/packages/docs/blog/2024-12-22-dockview-2.1.3.md b/packages/docs/blog/2024-12-22-dockview-2.1.3.md new file mode 100644 index 000000000..90d87179f --- /dev/null +++ b/packages/docs/blog/2024-12-22-dockview-2.1.3.md @@ -0,0 +1,19 @@ +--- +slug: dockview-2.1.3-release +title: Dockview 2.1.3 +tags: [release] +--- + +# Release Notes + +Please reference docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features + +## 🛠 Miscs + +- Bug: Fix rendering issue when popout group is moved into new grid group [#799](https://github.com/mathuo/dockview/issues/799) + +## 🔥 Breaking changes + + diff --git a/packages/docs/blog/2024-12-23-dockview-2.1.4.md b/packages/docs/blog/2024-12-23-dockview-2.1.4.md new file mode 100644 index 000000000..731c69478 --- /dev/null +++ b/packages/docs/blog/2024-12-23-dockview-2.1.4.md @@ -0,0 +1,20 @@ +--- +slug: dockview-2.1.4-release +title: Dockview 2.1.4 +tags: [release] +--- + +# Release Notes + +Please reference docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features + +## 🛠 Miscs + +- Maintenance: Cleanup resource dispose [#801](https://github.com/mathuo/dockview/issues/801) +- Bug: Fix for using popout groups with `always` rendering [#803](https://github.com/mathuo/dockview/issues/803) + +## 🔥 Breaking changes + + diff --git a/packages/docs/blog/2024-12-29-dockview-3.0.0.md b/packages/docs/blog/2024-12-29-dockview-3.0.0.md new file mode 100644 index 000000000..807a5945e --- /dev/null +++ b/packages/docs/blog/2024-12-29-dockview-3.0.0.md @@ -0,0 +1,29 @@ +--- +slug: dockview-3.0.0-release +title: Dockview 3.0.0 +tags: [release] +--- + +# Release Notes + +This is a major release version due to some breaking changes in the `dockview-core` package. If you use the react or vue versions of dockview you should not see any breaking changes when upgrading. There are no new features in this release. + +Please reference docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features + +## 🛠 Miscs + +- `dockview-vue` vue3 peerDependency [#808](https://github.com/mathuo/dockview/issues/808) +- Bug: correct enablement of `dv-single-tab` class [#811](https://github.com/mathuo/dockview/issues/811) + +## 🔥 Breaking changes + +- Standardize `dockview-core` components for generic framework extensions following the pattern in `DockviewComponent` [#810](https://github.com/mathuo/dockview/issues/810) + - `SplitviewComponent`: Replace `components` and `frameworkComponents` with `createComponent` + - `PaneviewComponent`: Replace `components` and `frameworkComponents` with `createComponent` and replace `headerComponents` and `headerFrameworkComponents` with `createHeaderComponent` + - `GridviewComponent`: Replace `components` and `frameworkComponents` with `createComponent` +- rename class `dockview-react-part` to `dv-react-part` [#806](https://github.com/mathuo/dockview/issues/806) +- rename type `PaneviewDropEvent` to `PaneviewDidDropEvent` [#812](https://github.com/mathuo/dockview/issues/812) +- remove `showDndOverlay` from `PaneviewComponent` in favour of `api.onUnhandledDragOverEvent` [#812](https://github.com/mathuo/dockview/issues/812) + diff --git a/packages/docs/blog/2025-01-09-dockview-3.0.1.md b/packages/docs/blog/2025-01-09-dockview-3.0.1.md new file mode 100644 index 000000000..cef014f95 --- /dev/null +++ b/packages/docs/blog/2025-01-09-dockview-3.0.1.md @@ -0,0 +1,17 @@ +--- +slug: dockview-3.0.1-release +title: Dockview 3.0.1 +tags: [release] +--- + +# Release Notes + +Please reference docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features + +## 🛠 Miscs + +## 🔥 Breaking changes + +- Fix duplicate HTML element [#810](https://github.com/mathuo/dockview/issues/818) diff --git a/packages/docs/blog/2025-01-11-dockview-3.0.2.md b/packages/docs/blog/2025-01-11-dockview-3.0.2.md new file mode 100644 index 000000000..f223b81bd --- /dev/null +++ b/packages/docs/blog/2025-01-11-dockview-3.0.2.md @@ -0,0 +1,18 @@ +--- +slug: dockview-3.0.2-release +title: Dockview 3.0.2 +tags: [release] +--- + +# Release Notes + +Please reference docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features + +## 🛠 Miscs + +## 🔥 Breaking changes + +- Fix issue when transitioning panel from floating to popout to floating [#810](https://github.com/mathuo/dockview/issues/824) +- Fix duplicate HTML containers [#825](https://github.com/mathuo/dockview/pull/825) diff --git a/packages/docs/blog/2025-02-02-dockview-3.1.0.md b/packages/docs/blog/2025-02-02-dockview-3.1.0.md new file mode 100644 index 000000000..124100509 --- /dev/null +++ b/packages/docs/blog/2025-02-02-dockview-3.1.0.md @@ -0,0 +1,22 @@ +--- +slug: dockview-3.1.0-release +title: Dockview 3.1.0 +tags: [release] +--- + +# Release Notes + +Please reference docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features + +- Close tab with middle mouse button [#847](https://github.com/mathuo/dockview/pull/847) + +## 🛠 Miscs + +- Bug: Fix crash on navigation with open popout group [#835](https://github.com/mathuo/dockview/pull/848) [#845](https://github.com/mathuo/dockview/pull/845) +- Bug: Subscribe to `onDidAcitvePanelChange` immediately, rather than deferred to `queueMicrotask` [#843](https://github.com/mathuo/dockview/pull/843) +- Bug: Minor theme fixup [#831](https://github.com/mathuo/dockview/pull/831) + +## 🔥 Breaking changes + diff --git a/packages/docs/blog/2025-02-09-dockview-3.1.1.md b/packages/docs/blog/2025-02-09-dockview-3.1.1.md new file mode 100644 index 000000000..925e98133 --- /dev/null +++ b/packages/docs/blog/2025-02-09-dockview-3.1.1.md @@ -0,0 +1,18 @@ +--- +slug: dockview-3.1.1-release +title: Dockview 3.1.1 +tags: [release] +--- + +# Release Notes + +Please reference docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features + +## 🛠 Miscs + +- Bug: Fix Middle mouse button to close tab [#835](https://github.com/mathuo/dockview/issues/853) + +## 🔥 Breaking changes + diff --git a/packages/docs/blog/2025-02-12-dockview-3.2.0.md b/packages/docs/blog/2025-02-12-dockview-3.2.0.md new file mode 100644 index 000000000..05b6e70e7 --- /dev/null +++ b/packages/docs/blog/2025-02-12-dockview-3.2.0.md @@ -0,0 +1,18 @@ +--- +slug: dockview-3.2.0-release +title: Dockview 3.2.0 +tags: [release] +--- + +# Release Notes + +Please reference docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features + +- Add CSS properties `--dv-active-sash-transition-duration` and `--dv-active-sash-transition-delay` [#835](https://github.com/mathuo/dockview/issues/859) + +## 🛠 Miscs + +## 🔥 Breaking changes + diff --git a/packages/docs/blog/2025-03-12-dockview-4.0.0.md b/packages/docs/blog/2025-03-12-dockview-4.0.0.md new file mode 100644 index 000000000..e7a759965 --- /dev/null +++ b/packages/docs/blog/2025-03-12-dockview-4.0.0.md @@ -0,0 +1,24 @@ +--- +slug: dockview-4.0.0-release +title: Dockview 4.0.0 +tags: [release] +--- + +# Release Notes + +Please reference docs @ [dockview.dev](https://dockview.dev). + +## 🚀 Features +- To control the theme of dockview you should no longer pass a `dv-theme-*` class, instead directly use the `theme` property. See [Themes](https://dockview.dev/demo) for more details. [#850](https://github.com/mathuo/dockview/pull/850) +- Introduces a new dnd overlay model with improved animations and customization options allowing themes to take more fine grained control over the dnd overlay styles. [#850](https://github.com/mathuo/dockview/pull/850) +- Custom scrollbar on tab headers for better UX [#822](https://github.com/mathuo/dockview/pull/822) +- When tabs are hidden within scrollbar hidden tabs can be selected from a dropdown that appears in header [#822](https://github.com/mathuo/dockview/pull/822) + +## 🛠 Miscs + +- Bug: Remove elements from DOM after disposable for Splitview and Paneview components [#870](https://github.com/mathuo/dockview/pull/870) + +## 🔥 Breaking changes + +- `setGap(gap: number | undefined): void` and `gap(): number` have been removed. The gap property is now controlled directly within the chosen Theme. See [Themes](https://dockview.dev) for more details. +- `DockviewDefaultTab` requires a `tabLocation` field, to mimic existing behaviour use `tabLocation="header"`. diff --git a/packages/docs/docs/advanced/nested.mdx b/packages/docs/docs/advanced/nested.mdx index 49161a44a..66f11c897 100644 --- a/packages/docs/docs/advanced/nested.mdx +++ b/packages/docs/docs/advanced/nested.mdx @@ -8,6 +8,6 @@ import { CodeRunner } from '@site/src/components/ui/codeRunner'; # 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`. +If you wish to interact with the drop event from one dockview instance in another dockview instance you can implement the `api.onUnhandledDragOverEvent` and `onDidDrop` props on `DockviewReact`. diff --git a/packages/docs/docs/core/dnd/dragAndDrop.mdx b/packages/docs/docs/core/dnd/dragAndDrop.mdx index ff73060ec..bb68e83c2 100644 --- a/packages/docs/docs/core/dnd/dragAndDrop.mdx +++ b/packages/docs/docs/core/dnd/dragAndDrop.mdx @@ -24,12 +24,12 @@ The dock makes heavy use of drag and drop functionalities. # Drag And Drop -You can override the conditions of the far edge overlays through the `rootOverlayModel` prop. +You can override the conditions of the far edge overlays through the `dndEdges` prop. ```tsx { const { group } = event; @@ -58,14 +58,17 @@ const onDidDrop = (event: DockviewDropEvent) => { }); }; -/** - * 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; -}; +const onReady = (event: DockviewReadyEvent) => { + + /** + * 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 + **/ + api.onUnhandledDragOverEvent(event => { + event.accept(); + }); +} return ( ); ``` diff --git a/packages/docs/docs/core/panels/add.mdx b/packages/docs/docs/core/panels/add.mdx index cb151eb6e..aceba5e9c 100644 --- a/packages/docs/docs/core/panels/add.mdx +++ b/packages/docs/docs/core/panels/add.mdx @@ -13,6 +13,8 @@ Panels can be added through the dock api. + + ## Opening a Basic Panel To open a panel requires a unique `id` and the name of the `component` to render. @@ -95,6 +97,8 @@ See [Panel Rendering](/docs/core/panels/rendering). You can position a panel relative to an existing panel, group using `direction`. If you do not provide a reference panel or group then the panel will be positioned to the edge of the dock in the specified direction. + + #### Relative to another Panel ```ts diff --git a/packages/docs/docs/overview/getStarted/theme.mdx b/packages/docs/docs/overview/getStarted/theme.mdx index 2e89eefe6..dc025b57c 100644 --- a/packages/docs/docs/overview/getStarted/theme.mdx +++ b/packages/docs/docs/overview/getStarted/theme.mdx @@ -6,8 +6,10 @@ title: Theme import { CSSVariablesTable, ThemeTable } from '@site/src/components/cssVariables'; +import { DocRef } from '@site/src/components/ui/reference/docRef'; -Theming is controlled through CSS and is highly customizable. +Dockview components accept a `theme` property which is highly customizable, the theme is largly controlled through CSS however some properties can only be adjusted +by direct editing variables of the `theme` object. Firstly, you should import `dockview.css`: @@ -32,15 +34,34 @@ Firstly, you should import `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` comes with a number of built-in themes. Each theme is represented as an object that can be imported. + +For dock components you should pass the theme object to the `theme` property, for other components such as split, pane and grid views you should +use set the themes associated CSS class to the `className` property. + +```tsx +import { themeAbyss } from "dockview"; + +// For dock components +theme={themeAbyss} + +// For other components +const {className} = themeAbyss; +``` :::info -The source code for all themes can be found [here](https://github.com/mathuo/dockview/blob/master/packages/dockview-core/src/theme.scss). +The source code for all themes can be found [here](https://github.com/mathuo/dockview/blob/master/packages/dockview-core/src/theme.scss) and the associated CSS [here](https://github.com/mathuo/dockview/blob/master/packages/dockview-core/src/theme.scss). ::: +## Build your own theme + +You can define your own `DockviewTheme` object and pass it to the `theme` property. + + + + ## Customizing Theme The provided themes are controlled primarily through a long list of CSS variables which can be modified by the user either entirely for a new theme diff --git a/packages/docs/docusaurus.config.js b/packages/docs/docusaurus.config.js index a1ad4ae14..97b77aeed 100644 --- a/packages/docs/docusaurus.config.js +++ b/packages/docs/docusaurus.config.js @@ -107,8 +107,8 @@ const config = { sidebarPath: require.resolve('./sidebars.js'), // Please change this to your repo. // Remove this to remove the "edit this page" links. - editUrl: - 'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/', + // editUrl: + // 'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/', // versions: { // current: { // label: `Development 🚧`, @@ -116,11 +116,11 @@ const config = { // }, }, blog: { - showReadingTime: true, + // showReadingTime: true, // Please change this to your repo. // Remove this to remove the "edit this page" links. - editUrl: - 'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/', + // editUrl: + // 'https://github.com/facebook/docusaurus/tree/main/packages/create-docusaurus/templates/shared/', }, theme: { customCss: require.resolve('./src/css/custom.scss'), @@ -184,17 +184,8 @@ const config = { label: 'API', }, { to: '/blog', label: 'Blog', position: 'left' }, + { href: '/templates', target:"_blank", label: 'Examples', position: 'left' }, { to: '/demo', label: 'Demo', position: 'left' }, - // { - // to: 'https://dockview.dev/typedocs', - // label: 'TSDoc', - // position: 'left', - // }, - - // { - // type: 'docsVersionDropdown', - // position: 'right', - // }, { href: 'https://github.com/mathuo/dockview', position: 'right', @@ -210,8 +201,8 @@ const config = { title: 'Learn', items: [ { - label: 'Docs', - to: '/docs', + label: 'Demo', + to: '/demo', }, ], }, diff --git a/packages/docs/old_docs_DELETE_SOON/components/paneview/paneview.mdx b/packages/docs/old_docs_DELETE_SOON/components/paneview/paneview.mdx index 95e025478..b88776771 100644 --- a/packages/docs/old_docs_DELETE_SOON/components/paneview/paneview.mdx +++ b/packages/docs/old_docs_DELETE_SOON/components/paneview/paneview.mdx @@ -221,7 +221,7 @@ If you provide the `PaneviewReact` component with the prop `onDidDrop` you will 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`. +If you wish to interact with the drop event from one paneview instance in another paneview instance you can implement the `api.onUnhandledDragOverEvent` 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/package.json b/packages/docs/package.json index 25f23a5df..2a0cef3d2 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "dockview-docs", - "version": "2.0.0", + "version": "4.0.0", "private": true, "scripts": { "build": "npm run build-templates && docusaurus build", @@ -38,7 +38,7 @@ "ag-grid-react": "^31.0.2", "axios": "^1.6.3", "clsx": "^2.1.0", - "dockview": "^2.0.0", + "dockview": "^4.0.0", "prism-react-renderer": "^2.3.1", "react-dnd": "^16.0.1", "react-laag": "^2.0.5", diff --git a/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.scss b/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.scss index 6abc9a64d..2f7a940fc 100644 --- a/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.scss +++ b/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.scss @@ -1,33 +1,30 @@ -.group-control { - .action { - padding: 4px; - display: flex; - align-items: center; - justify-content: center; - box-sizing: border-box; - font-size: 18px; - cursor: pointer; +.dockview-demo { + .group-control { + .action { + padding: 4px; + display: flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + font-size: 18px; + cursor: pointer; - &:hover { - border-radius: 2px; - background-color: var(--dv-icon-hover-background-color); + &:hover { + border-radius: 2px; + color: var(--dv-activegroup-visiblepanel-tab-color); + background-color: var(--dv-icon-hover-background-color); + } } } -} -.data-table { - table { - font-size: 11px; - th { - padding: 0px 8px; + .data-table { + table { + font-size: 11px; + th { + padding: 0px 8px; + } } } -} - -.action-container { - display: flex; - padding: 4px 0px; - overflow: auto; button { height: 25px; @@ -40,57 +37,78 @@ cursor: pointer; outline: 1px solid #4c65d4; + &:focus { + outline: 1px solid #4c65d4 !important; + } + &:hover { background-color: #222e62; } } - .text-button { - margin: 0px 4px; - } - - .button-action { - margin: 0px 4px; - // display: flex; - - .selected { - background-color: #4864dc; - } - } - - .button-group { - button { - margin-right: 0px; - } - } - - .demo-button { - min-width: 50px; - padding: 0px 2px; - border-radius: 0px; - display: flex; - flex-grow: 1; - align-items: center; + input { outline: 1px solid #4c65d4; - } - - .demo-icon-button { - outline: 1px solid #4c65d4; - flex-grow: 1; - display: flex; - align-items: center; - border-radius: 0px; - padding: 0px 4px; border: none; - cursor: pointer; + margin: 0px; + height: 25px; - &:disabled { - color: gray; - cursor: help; + &:focus { + outline: 1px solid #4c65d4 !important; + } + } + + .action-container { + display: flex; + padding: 4px; + overflow: auto; + + .text-button { + margin: 0px 4px; } - span { - font-size: 16px; + .button-action { + margin: 0px 4px; + // display: flex; + + .selected { + background-color: #4864dc; + } + } + + .button-group { + button { + margin-right: 0px; + } + } + + .demo-button { + min-width: 50px; + padding: 0px 2px; + border-radius: 0px; + display: flex; + flex-grow: 1; + align-items: center; + outline: 1px solid #4c65d4; + } + + .demo-icon-button { + outline: 1px solid #4c65d4; + flex-grow: 1; + display: flex; + align-items: center; + border-radius: 0px; + padding: 0px 4px; + border: none; + cursor: pointer; + + &:disabled { + color: gray; + cursor: help; + } + + span { + font-size: 16px; + } } } } diff --git a/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx b/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx index 7f1d8d8b4..7c9fe42f7 100644 --- a/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx +++ b/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx @@ -5,6 +5,7 @@ import { IDockviewPanelHeaderProps, IDockviewPanelProps, DockviewApi, + DockviewTheme, } from 'dockview'; import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; @@ -95,6 +96,7 @@ const components = { ); }, nested: (props: IDockviewPanelProps) => { + const theme = React.useContext(ThemeContext); return ( { + console.log('remove', e); }); }} - className={'dockview-theme-abyss'} + theme={theme} /> ); }, @@ -181,7 +186,9 @@ const WatermarkComponent = () => { return
custom watermark
; }; -const DockviewDemo = (props: { theme?: string }) => { +const ThemeContext = React.createContext(undefined); + +const DockviewDemo = (props: { theme?: DockviewTheme }) => { const [logLines, setLogLines] = React.useState< { text: string; timestamp?: Date; backgroundColor?: string }[] >([]); @@ -216,75 +223,92 @@ const DockviewDemo = (props: { theme?: string }) => { setPending([]); }, [pending]); - const onReady = (event: DockviewReadyEvent) => { - setApi(event.api); - - event.api.onDidAddPanel((event) => { - setPanels((_) => [..._, event.id]); - addLogLine(`Panel Added ${event.id}`); - }); - event.api.onDidActivePanelChange((event) => { - setActivePanel(event?.id); - addLogLine(`Panel Activated ${event?.id}`); - }); - event.api.onDidRemovePanel((event) => { - setPanels((_) => { - const next = [..._]; - next.splice( - next.findIndex((x) => x === event.id), - 1 - ); - - return next; - }); - addLogLine(`Panel Removed ${event.id}`); - }); - - event.api.onDidAddGroup((event) => { - setGroups((_) => [..._, event.id]); - addLogLine(`Group Added ${event.id}`); - }); - - event.api.onDidMovePanel((event) => { - addLogLine(`Panel Moved ${event.panel.id}`); - }); - - event.api.onDidMaximizedGroupChange((event) => { - addLogLine( - `Group Maximized Changed ${event.group.id} [${event.isMaximized}]` - ); - }); - - event.api.onDidRemoveGroup((event) => { - setGroups((_) => { - const next = [..._]; - next.splice( - next.findIndex((x) => x === event.id), - 1 - ); - - return next; - }); - addLogLine(`Group Removed ${event.id}`); - }); - - event.api.onDidActiveGroupChange((event) => { - setActiveGroup(event?.id); - addLogLine(`Group Activated ${event?.id}`); - }); - - const state = localStorage.getItem('dv-demo-state'); - if (state) { - try { - event.api.fromJSON(JSON.parse(state)); - return; - } catch { - localStorage.removeItem('dv-demo-state'); - } + React.useEffect(() => { + if (!api) { return; } - defaultConfig(event.api); + const disposables = [ + api.onDidAddPanel((event) => { + setPanels((_) => [..._, event.id]); + addLogLine(`Panel Added ${event.id}`); + }), + api.onDidActivePanelChange((event) => { + setActivePanel(event?.id); + addLogLine(`Panel Activated ${event?.id}`); + }), + api.onDidRemovePanel((event) => { + setPanels((_) => { + const next = [..._]; + next.splice( + next.findIndex((x) => x === event.id), + 1 + ); + + return next; + }); + addLogLine(`Panel Removed ${event.id}`); + }), + + api.onDidAddGroup((event) => { + setGroups((_) => [..._, event.id]); + addLogLine(`Group Added ${event.id}`); + }), + + api.onDidMovePanel((event) => { + addLogLine(`Panel Moved ${event.panel.id}`); + }), + + api.onDidMaximizedGroupChange((event) => { + addLogLine( + `Group Maximized Changed ${event.group.api.id} [${event.isMaximized}]` + ); + }), + + api.onDidRemoveGroup((event) => { + setGroups((_) => { + const next = [..._]; + next.splice( + next.findIndex((x) => x === event.id), + 1 + ); + + return next; + }); + addLogLine(`Group Removed ${event.id}`); + }), + + api.onDidActiveGroupChange((event) => { + setActiveGroup(event?.id); + addLogLine(`Group Activated ${event?.id}`); + }), + ]; + + const loadLayout = () => { + const state = localStorage.getItem('dv-demo-state'); + + if (state) { + try { + api.fromJSON(JSON.parse(state)); + return; + } catch { + localStorage.removeItem('dv-demo-state'); + } + return; + } + + defaultConfig(api); + }; + + loadLayout(); + + return () => { + disposables.forEach((disposable) => disposable.dispose()); + }; + }, [api]); + + const onReady = (event: DockviewReadyEvent) => { + setApi(event.api); }; const [watermark, setWatermark] = React.useState(false); @@ -307,6 +331,7 @@ const DockviewDemo = (props: { theme?: string }) => { return (
{ style={{ flexGrow: 1, overflow: 'hidden', - height: '100%', display: 'flex', }} > - + + +
@@ -424,61 +452,77 @@ const DockviewDemo = (props: { theme?: string }) => { width: '400px', backgroundColor: 'black', color: 'white', - overflow: 'auto', + overflow: 'hidden', fontFamily: 'monospace', marginLeft: '10px', flexShrink: 0, + display: 'flex', + flexDirection: 'column', }} > - {logLines.map((line, i) => { - return ( -
- + {logLines.map((line, i) => { + return ( +
- {logLines.length - i} - - - {line.timestamp && ( - - {line.timestamp - .toISOString() - .substring(11, 23)} - - )} - {line.text} - -
- ); - })} + + {logLines.length - i} + + + {line.timestamp && ( + + {line.timestamp + .toISOString() + .substring(11, 23)} + + )} + {line.text} + +
+ ); + })} +
+
+ +
)}
diff --git a/packages/docs/sandboxes/react/dockview/demo-dockview/src/controls.tsx b/packages/docs/sandboxes/react/dockview/demo-dockview/src/controls.tsx index 63032b5f4..c9fd5e19f 100644 --- a/packages/docs/sandboxes/react/dockview/demo-dockview/src/controls.tsx +++ b/packages/docs/sandboxes/react/dockview/demo-dockview/src/controls.tsx @@ -81,7 +81,7 @@ export const RightControls = (props: IDockviewHeaderActionsProps) => { alignItems: 'center', padding: '0px 8px', height: '100%', - color: 'var(--dv-activegroup-visiblepanel-tab-color)', + color: 'var(--dv-activegroup-hiddenpanel-tab-color)', }} > {props.isGroupActive && } diff --git a/packages/docs/sandboxes/react/dockview/demo-dockview/src/gridActions.tsx b/packages/docs/sandboxes/react/dockview/demo-dockview/src/gridActions.tsx index 7087347b6..5c13128f5 100644 --- a/packages/docs/sandboxes/react/dockview/demo-dockview/src/gridActions.tsx +++ b/packages/docs/sandboxes/react/dockview/demo-dockview/src/gridActions.tsx @@ -2,7 +2,7 @@ import { DockviewApi } from 'dockview'; import * as React from 'react'; import { defaultConfig, nextId } from './defaultLayout'; -import { createRoot, Root } from 'react-dom/client'; +import { createRoot } from 'react-dom/client'; import { PanelBuilder } from './panelBuilder'; let mount = document.querySelector('.popover-anchor') as HTMLElement | null; @@ -132,10 +132,7 @@ export const GridActions = (props: { const popover = usePopover(); - const onAddPanel = (options?: { - advanced: boolean; - component?: string; - }) => { + const onAddPanel = (options?: { advanced?: boolean; nested?: boolean }) => { if (options?.advanced) { popover.open(({ close }) => { return ; @@ -143,7 +140,7 @@ export const GridActions = (props: { } else { props.api?.addPanel({ id: `id_${Date.now().toString()}`, - component: options?.component ?? 'default', + component: options?.nested ? 'nested' : 'default', title: `Tab ${nextId()}`, renderer: 'always', }); @@ -154,12 +151,6 @@ export const GridActions = (props: { props.api?.addGroup(); }; - const [gap, setGap] = React.useState(0); - - React.useEffect(() => { - props.api?.setGap(gap); - }, [gap, props.api]); - return (
@@ -173,22 +164,12 @@ export const GridActions = (props: { tune
-
- - -
+ @@ -217,18 +198,6 @@ export const GridActions = (props: { Reset -
- Group Gap - setGap(Number(event.target.value))} - /> -
); }; diff --git a/packages/docs/sandboxes/react/dockview/demo-dockview/src/groupActions.tsx b/packages/docs/sandboxes/react/dockview/demo-dockview/src/groupActions.tsx index 815621b72..0af2a66cc 100644 --- a/packages/docs/sandboxes/react/dockview/demo-dockview/src/groupActions.tsx +++ b/packages/docs/sandboxes/react/dockview/demo-dockview/src/groupActions.tsx @@ -88,7 +88,6 @@ const GroupAction = (props: { } onClick={() => { if (group) { - props.api.addFloatingGroup(group, { width: 400, height: 300, @@ -99,7 +98,6 @@ const GroupAction = (props: { right: 50, }, }); - } }} > diff --git a/packages/docs/sandboxes/react/dockview/demo-dockview/src/mapboxPanel.tsx b/packages/docs/sandboxes/react/dockview/demo-dockview/src/mapboxPanel.tsx new file mode 100644 index 000000000..3393133a3 --- /dev/null +++ b/packages/docs/sandboxes/react/dockview/demo-dockview/src/mapboxPanel.tsx @@ -0,0 +1,28 @@ +import { IDockviewPanelProps } from 'dockview'; +import * as React from 'react'; +import Map from 'react-map-gl'; + +export const MapboxPanel = (props: IDockviewPanelProps) => { + React.useEffect(() => { + const subscription = props.api.onDidLocationChange((e) => { + const isPopout = e.location.type === 'popout'; + }); + + return () => subscription.dispose(); + }, [props.api]); + + return ( +
+ +
+ ); +}; diff --git a/packages/docs/sandboxes/react/dockview/demo-dockview/src/panelActions.tsx b/packages/docs/sandboxes/react/dockview/demo-dockview/src/panelActions.tsx index d12cd06ac..276aaa94e 100644 --- a/packages/docs/sandboxes/react/dockview/demo-dockview/src/panelActions.tsx +++ b/packages/docs/sandboxes/react/dockview/demo-dockview/src/panelActions.tsx @@ -74,14 +74,7 @@ const PanelAction = (props: { onClick={() => { const panel = props.api.getPanel(props.panelId); if (panel) { - props.api.addFloatingGroup(panel, { - width: 400, - height: 300, - position: { - bottom: 50, - right: 50, - }, - }); + props.api.addFloatingGroup(panel); } }} > diff --git a/packages/docs/sandboxes/react/dockview/dnd-external/src/app.tsx b/packages/docs/sandboxes/react/dockview/dnd-external/src/app.tsx index 8d3c70cad..1fb061abc 100644 --- a/packages/docs/sandboxes/react/dockview/dnd-external/src/app.tsx +++ b/packages/docs/sandboxes/react/dockview/dnd-external/src/app.tsx @@ -113,7 +113,12 @@ const DndDockview = (props: { renderVisibleOnly: boolean; theme?: string }) => { } }); + const disposable = api.onUnhandledDragOverEvent((event) => { + event.accept(); + }); + return () => { + disposable.dispose(); panelDragDisposable.dispose(); groupDragDisposable.dispose(); }; @@ -134,10 +139,6 @@ const DndDockview = (props: { renderVisibleOnly: boolean; theme?: string }) => { }); }; - const showDndOverlay = (event: DockviewDndOverlayEvent) => { - return true; - }; - const onDrop = (event: React.DragEvent) => { const dataTransfer = event.dataTransfer; @@ -179,8 +180,7 @@ const DndDockview = (props: { renderVisibleOnly: boolean; theme?: string }) => { onReady={onReady} className={`${props.theme || 'dockview-theme-abyss'}`} onDidDrop={onDidDrop} - showDndOverlay={showDndOverlay} - rootOverlayModel={{ + dndEdges={{ size: { value: 100, type: 'pixels' }, activationSize: { value: 5, type: 'percentage' }, }} diff --git a/packages/docs/scripts/buildTemplates.mjs b/packages/docs/scripts/buildTemplates.mjs index f2cc212ce..555d4fd2d 100644 --- a/packages/docs/scripts/buildTemplates.mjs +++ b/packages/docs/scripts/buildTemplates.mjs @@ -1,7 +1,6 @@ import fs from 'fs-extra'; import * as path from 'path'; import { argv } from 'process'; -import { execSync } from 'child_process'; import { fileURLToPath } from 'url'; @@ -86,7 +85,8 @@ function createIndexHTML(options) { .map(([key, value]) => `"${key}": "${value}"`) .join(',\n')}` ) - .replace('{{app}}', options.app); + .replace('{{app}}', options.app) + .replace('{{githubLink}}', options.githubUrl) } const input_dir = path.join(__dirname, '../templates'); @@ -97,6 +97,8 @@ const FRAMEWORKS = ['react', 'vue', 'typescript']; const list = []; +const githubUrl = "https://github.com/mathuo/dockview/tree/master/packages/docs/templates" + for (const component of COMPONENTS) { const componentDir = path.join(input_dir, component); @@ -115,6 +117,9 @@ for (const component of COMPONENTS) { path.join(componentDir, folder, framework, 'src'), path.join(output, component, folder, framework, 'src') ); + + const templateGithubUrl = `${githubUrl}/${component}/${folder}/${framework}/src` + const template = createIndexHTML({ title: `Dockview | ${folder} ${framework}`, app: @@ -127,6 +132,7 @@ for (const component of COMPONENTS) { USE_LOCAL_CDN ? 'local' : 'remote' ], }, + githubUrl: templateGithubUrl }); fs.writeFileSync( path.join(output, component, folder, framework, 'index.html'), diff --git a/packages/docs/scripts/template.html b/packages/docs/scripts/template.html index 56a5a9ebe..7dda559be 100644 --- a/packages/docs/scripts/template.html +++ b/packages/docs/scripts/template.html @@ -12,7 +12,7 @@ @@ -62,9 +82,18 @@ -
- + + { return ( { const JavascriptIcon = (props: { height: number; width: number }) => { return ( { return (
{ diff --git a/packages/docs/src/components/ui/reference/docRef.tsx b/packages/docs/src/components/ui/reference/docRef.tsx index 8319d1721..2fccfdd9e 100644 --- a/packages/docs/src/components/ui/reference/docRef.tsx +++ b/packages/docs/src/components/ui/reference/docRef.tsx @@ -192,6 +192,8 @@ function filter(docs: TypeSystem.Type, methods: string[]) { .map((v) => filter(v, methods)) .flat(); } + + return [docs]; } if (docs.kind === 'class' || docs.kind === 'interface') { diff --git a/packages/docs/src/config/theme.config.ts b/packages/docs/src/config/theme.config.ts index 2e8b9741f..e509c2178 100644 --- a/packages/docs/src/config/theme.config.ts +++ b/packages/docs/src/config/theme.config.ts @@ -1,33 +1,54 @@ +import { + themeAbyss, + themeDark, + themeDracula, + themeAbyssSpaced, + themeLightSpaced, + themeLight, + themeReplit, + themeVisualStudio, +} from 'dockview'; + export const themeConfig = [ { - id: 'dockview-theme-dark', - key: '**[dockview-theme-dark](/demo?theme=dockview-theme-dark)**', + id: themeDark, + key: '**[Dark](/demo?theme=dark)**', text: '', }, { - id: 'dockview-theme-light', - key: '**[dockview-theme-light](/demo?theme=dockview-theme-light)**', + id: themeLight, + key: '**[Light](/demo?theme=light)**', text: '', }, { - id: 'dockview-theme-vs', - key: '**[dockview-theme-vs](/demo?theme=dockview-theme-vs)**', + id: themeVisualStudio, + key: '**[Visual Studio](/demo?theme=visualStudio)**', text: 'Based on [Visual Studio](https://visualstudio.microsoft.com)', }, { - id: 'dockview-theme-abyss', - key: '**[dockview-theme-abyss](/demo?theme=dockview-theme-abyss)**', + id: themeAbyss, + key: '**[Abyss](/demo?theme=abyss)**', text: 'Based on [Visual Studio Code](https://code.visualstudio.com/docs/getstarted/themes) abyss theme', }, { - id: 'dockview-theme-dracula', - key: '**[dockview-theme-dracula](/demo?theme=dockview-theme-dracula)**', + id: themeDracula, + key: '**[Dracula](/demo?theme=dracula)**', text: 'Based on [Visual Studio Code](https://code.visualstudio.com/docs/getstarted/themes) dracula theme', }, { - id: 'dockview-theme-replit', - key: '**[dockview-theme-replit](/demo?theme=dockview-theme-replit)**', + id: themeReplit, + key: '**[Replit](/demo?theme=replit)**', text: 'Based on [Replit](https://replit.com)', }, + { + id: themeLightSpaced, + key: '**[Light Spaced](/demo?theme=lightSpaced)**', + text: '', + }, + { + id: themeAbyssSpaced, + key: '**[Abyss Spaced](/demo?theme=abyssSpaced)**', + text: '', + }, ]; diff --git a/packages/docs/src/css/custom.scss b/packages/docs/src/css/custom.scss index 9d446add3..0ab665892 100644 --- a/packages/docs/src/css/custom.scss +++ b/packages/docs/src/css/custom.scss @@ -11,10 +11,10 @@ /* You can override the default Infima variables here. */ :root { - --ifm-font-family-base: "IBM Plex Sans", ui-sans-serif, system-ui, -apple-system, - BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, - sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, - Noto Color Emoji; + --ifm-font-family-base: 'IBM Plex Sans', ui-sans-serif, system-ui, + -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, + Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, + Segoe UI Symbol, Noto Color Emoji; --ifm-font-weight-bold: 600; @@ -36,6 +36,9 @@ --ifm-color-primary: black; + --ifm-dropdown-background-color: white; + --ifm-dropdown-border: 1px solid var(--ifm-color-primary-darkest); + --ifm-navbar-link-color: white; --ifm-navbar-link-hover-color: white; @@ -54,15 +57,18 @@ } /* --ifm-color-primary: #0c111d; */ - --ifm-color-primary: #25c2a0; - --ifm-color-primary-dark: #21af90; - --ifm-color-primary-darker: #1fa588; - --ifm-color-primary-darkest: #1a8870; - --ifm-color-primary-light: #29d5b0; - --ifm-color-primary-lighter: #32d8b4; - --ifm-color-primary-lightest: #4fddbf; + --ifm-color-primary: #98a2b3; + --ifm-color-primary-dark: #828a99; + --ifm-color-primary-darker: #6a707c; + --ifm-color-primary-darkest: #474b53; + --ifm-color-primary-light: #acb7ca; + --ifm-color-primary-lighter: #bcc9df; + --ifm-color-primary-lightest: #d2e1fa; --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); + --ifm-dropdown-background-color: #373d4b; + --ifm-dropdown-border: 1px solid var(--ifm-color-primary-darkest); + --dv-docs-markdown-text-color: #cdced8; } diff --git a/packages/docs/src/generated/api.output.json b/packages/docs/src/generated/api.output.json index d7024baab..f8a8a944a 100644 --- a/packages/docs/src/generated/api.output.json +++ b/packages/docs/src/generated/api.output.json @@ -315,6 +315,34 @@ "isReadonly": true } }, + { + "name": "onDidMaximizedChange", + "code": "Event>", + "kind": "property", + "type": { + "type": "reference", + "value": "Event", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "MaximizedChanged", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "T", + "source": "dockview-core", + "refersToTypeParameter": true + } + ] + } + ] + }, + "flags": { + "isReadonly": true + } + }, { "name": "onDidRemove", "code": "Event", @@ -540,27 +568,6 @@ } } }, - { - "name": "onDidMaximizedGroupChange", - "code": "Event", - "kind": "accessor", - "value": { - "name": "onDidMaximizedGroupChange", - "code": "Event", - "kind": "getSignature", - "returnType": { - "type": "reference", - "value": "Event", - "source": "dockview-core", - "typeArguments": [ - { - "type": "intrinsic", - "value": "void" - } - ] - } - } - }, { "name": "size", "code": "number", @@ -1824,20 +1831,6 @@ ] } }, - { - "name": "gap", - "code": "number", - "kind": "accessor", - "value": { - "name": "gap", - "code": "number", - "kind": "getSignature", - "returnType": { - "type": "intrinsic", - "value": "number" - } - } - }, { "name": "groups", "code": "DockviewGroupPanel[]", @@ -2352,11 +2345,11 @@ }, { "name": "onDidMaximizedGroupChange", - "code": "Event", + "code": "Event", "kind": "accessor", "value": { "name": "onDidMaximizedGroupChange", - "code": "Event", + "code": "Event", "kind": "getSignature", "returnType": { "type": "reference", @@ -2364,8 +2357,9 @@ "source": "dockview-core", "typeArguments": [ { - "type": "intrinsic", - "value": "void" + "type": "reference", + "value": "DockviewMaximizedGroupChanged", + "source": "dockview-core" } ] } @@ -2833,7 +2827,7 @@ }, { "name": "addFloatingGroup", - "code": "(item: IDockviewPanel | DockviewGroupPanel, options?: FloatingGroupOptions): void", + "code": "(item: DockviewGroupPanel | IDockviewPanel, options?: FloatingGroupOptions): void", "kind": "method", "signature": [ { @@ -2850,18 +2844,18 @@ "parameters": [ { "name": "item", - "code": "item: IDockviewPanel | DockviewGroupPanel", + "code": "item: DockviewGroupPanel | IDockviewPanel", "type": { "type": "or", "values": [ { "type": "reference", - "value": "IDockviewPanel", + "value": "DockviewGroupPanel", "source": "dockview-core" }, { "type": "reference", - "value": "DockviewGroupPanel", + "value": "IDockviewPanel", "source": "dockview-core" } ] @@ -2883,7 +2877,7 @@ "type": "intrinsic", "value": "void" }, - "code": "(item: IDockviewPanel | DockviewGroupPanel, options?: FloatingGroupOptions): void", + "code": "(item: DockviewGroupPanel | IDockviewPanel, options?: FloatingGroupOptions): void", "kind": "callSignature" } ], @@ -2898,7 +2892,7 @@ }, { "name": "addGroup", - "code": "(options?: GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup | GroupOptions & AbsolutePosition): DockviewGroupPanel", + "code": "(options?: GroupOptions & AbsolutePosition | GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup): DockviewGroupPanel", "kind": "method", "signature": [ { @@ -2915,10 +2909,25 @@ "parameters": [ { "name": "options", - "code": "options?: GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup | GroupOptions & AbsolutePosition", + "code": "options?: GroupOptions & AbsolutePosition | GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup", "type": { "type": "or", "values": [ + { + "type": "intersection", + "values": [ + { + "type": "reference", + "value": "GroupOptions", + "source": "dockview-core" + }, + { + "type": "reference", + "value": "AbsolutePosition", + "source": "dockview-core" + } + ] + }, { "type": "intersection", "values": [ @@ -2948,21 +2957,6 @@ "source": "dockview-core" } ] - }, - { - "type": "intersection", - "values": [ - { - "type": "reference", - "value": "GroupOptions", - "source": "dockview-core" - }, - { - "type": "reference", - "value": "AbsolutePosition", - "source": "dockview-core" - } - ] } ] }, @@ -2974,7 +2968,7 @@ "value": "DockviewGroupPanel", "source": "dockview-core" }, - "code": "(options?: GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup | GroupOptions & AbsolutePosition): DockviewGroupPanel", + "code": "(options?: GroupOptions & AbsolutePosition | GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup): DockviewGroupPanel", "kind": "callSignature" } ], @@ -3052,7 +3046,7 @@ }, { "name": "addPopoutGroup", - "code": "(item: IDockviewPanel | DockviewGroupPanel, options?: { onDidOpen?: (event: { id: string, window: Window }): void, onWillClose?: (event: { id: string, window: Window }): void, popoutUrl?: string, position?: Box }): Promise", + "code": "(item: DockviewGroupPanel | IDockviewPanel, options?: { onDidOpen?: (event: { id: string, window: Window }): void, onWillClose?: (event: { id: string, window: Window }): void, popoutUrl?: string, position?: Box }): Promise", "kind": "method", "signature": [ { @@ -3069,18 +3063,18 @@ "parameters": [ { "name": "item", - "code": "item: IDockviewPanel | DockviewGroupPanel", + "code": "item: DockviewGroupPanel | IDockviewPanel", "type": { "type": "or", "values": [ { "type": "reference", - "value": "IDockviewPanel", + "value": "DockviewGroupPanel", "source": "dockview-core" }, { "type": "reference", - "value": "DockviewGroupPanel", + "value": "IDockviewPanel", "source": "dockview-core" } ] @@ -3271,7 +3265,7 @@ } ] }, - "code": "(item: IDockviewPanel | DockviewGroupPanel, options?: { onDidOpen?: (event: { id: string, window: Window }): void, onWillClose?: (event: { id: string, window: Window }): void, popoutUrl?: string, position?: Box }): Promise", + "code": "(item: DockviewGroupPanel | IDockviewPanel, options?: { onDidOpen?: (event: { id: string, window: Window }): void, onWillClose?: (event: { id: string, window: Window }): void, popoutUrl?: string, position?: Box }): Promise", "kind": "callSignature" } ], @@ -3928,43 +3922,6 @@ ] } }, - { - "name": "setGap", - "code": "(gap: number | undefined): void", - "kind": "method", - "signature": [ - { - "name": "setGap", - "typeParameters": [], - "parameters": [ - { - "name": "gap", - "code": "gap: number | undefined", - "type": { - "type": "or", - "values": [ - { - "type": "intrinsic", - "value": "number" - }, - { - "type": "intrinsic", - "value": "undefined" - } - ] - }, - "kind": "parameter" - } - ], - "returnType": { - "type": "intrinsic", - "value": "void" - }, - "code": "(gap: number | undefined): void", - "kind": "callSignature" - } - ] - }, { "name": "toJSON", "code": "(): SerializedDockview", @@ -4356,6 +4313,53 @@ "isReadonly": true } }, + { + "name": "onDidMaximizedChange", + "code": "Event>", + "kind": "property", + "type": { + "type": "reference", + "value": "Event", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "MaximizedChanged", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "DockviewGroupPanel", + "source": "dockview-core" + } + ] + } + ] + }, + "flags": { + "isReadonly": true + } + }, + { + "name": "onDidMaximizedGroupChange", + "code": "Event", + "kind": "property", + "type": { + "type": "reference", + "value": "Event", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "DockviewMaximizedGroupChanged", + "source": "dockview-core" + } + ] + }, + "flags": { + "isReadonly": true + } + }, { "name": "onDidMovePanel", "code": "Event", @@ -4376,6 +4380,25 @@ "isReadonly": true } }, + { + "name": "onDidOptionsChange", + "code": "Event", + "kind": "property", + "type": { + "type": "reference", + "value": "Event", + "source": "dockview-core", + "typeArguments": [ + { + "type": "intrinsic", + "value": "void" + } + ] + }, + "flags": { + "isReadonly": true + } + }, { "name": "onDidRemove", "code": "Event", @@ -4568,6 +4591,32 @@ "isReadonly": true } }, + { + "name": "popupService", + "code": "PopupService", + "kind": "property", + "type": { + "type": "reference", + "value": "PopupService", + "source": "dockview-core" + }, + "flags": { + "isReadonly": true + } + }, + { + "name": "rootDropTargetContainer", + "code": "DropTargetAnchorContainer", + "kind": "property", + "type": { + "type": "reference", + "value": "DropTargetAnchorContainer", + "source": "dockview-core" + }, + "flags": { + "isReadonly": true + } + }, { "name": "activeGroup", "code": "BaseGrid.T | undefined", @@ -4679,20 +4728,6 @@ } } }, - { - "name": "gap", - "code": "number", - "kind": "accessor", - "value": { - "name": "gap", - "code": "number", - "kind": "getSignature", - "returnType": { - "type": "intrinsic", - "value": "number" - } - } - }, { "name": "groups", "code": "BaseGrid.T[]", @@ -4824,27 +4859,6 @@ } } }, - { - "name": "onDidMaximizedGroupChange", - "code": "Event", - "kind": "accessor", - "value": { - "name": "onDidMaximizedGroupChange", - "code": "Event", - "kind": "getSignature", - "returnType": { - "type": "reference", - "value": "Event", - "source": "dockview-core", - "typeArguments": [ - { - "type": "intrinsic", - "value": "void" - } - ] - } - } - }, { "name": "options", "code": "DockviewComponentOptions", @@ -5033,7 +5047,7 @@ }, { "name": "addGroup", - "code": "(options?: GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup | GroupOptions & AbsolutePosition): DockviewGroupPanel", + "code": "(options?: GroupOptions & AbsolutePosition | GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup): DockviewGroupPanel", "kind": "method", "signature": [ { @@ -5042,10 +5056,25 @@ "parameters": [ { "name": "options", - "code": "options?: GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup | GroupOptions & AbsolutePosition", + "code": "options?: GroupOptions & AbsolutePosition | GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup", "type": { "type": "or", "values": [ + { + "type": "intersection", + "values": [ + { + "type": "reference", + "value": "GroupOptions", + "source": "dockview-core" + }, + { + "type": "reference", + "value": "AbsolutePosition", + "source": "dockview-core" + } + ] + }, { "type": "intersection", "values": [ @@ -5075,21 +5104,6 @@ "source": "dockview-core" } ] - }, - { - "type": "intersection", - "values": [ - { - "type": "reference", - "value": "GroupOptions", - "source": "dockview-core" - }, - { - "type": "reference", - "value": "AbsolutePosition", - "source": "dockview-core" - } - ] } ] }, @@ -5101,7 +5115,7 @@ "value": "DockviewGroupPanel", "source": "dockview-core" }, - "code": "(options?: GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup | GroupOptions & AbsolutePosition): DockviewGroupPanel", + "code": "(options?: GroupOptions & AbsolutePosition | GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup): DockviewGroupPanel", "kind": "callSignature" } ] @@ -5155,7 +5169,7 @@ }, { "name": "addPopoutGroup", - "code": "(itemToPopout: DockviewPanel | DockviewGroupPanel, options?: { onDidOpen?: (event: { id: string, window: Window }): void, onWillClose?: (event: { id: string, window: Window }): void, overridePopoutGroup?: DockviewGroupPanel, popoutUrl?: string, position?: Box }): Promise", + "code": "(itemToPopout: DockviewPanel | DockviewGroupPanel, options?: DockviewPopoutGroupOptions): Promise", "kind": "method", "signature": [ { @@ -5184,186 +5198,11 @@ }, { "name": "options", - "code": "options?: { onDidOpen?: (event: { id: string, window: Window }): void, onWillClose?: (event: { id: string, window: Window }): void, overridePopoutGroup?: DockviewGroupPanel, popoutUrl?: string, position?: Box }", + "code": "options?: DockviewPopoutGroupOptions", "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "{ onDidOpen?: (event: { id: string, window: Window }): void, onWillClose?: (event: { id: string, window: Window }): void, overridePopoutGroup?: DockviewGroupPanel, popoutUrl?: string, position?: Box }", - "kind": "typeLiteral", - "properties": [ - { - "name": "onDidOpen", - "code": "(event: { id: string, window: Window }): void", - "kind": "property", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "(event: { id: string, window: Window }): void", - "kind": "typeLiteral", - "signatures": [ - { - "name": "__type", - "typeParameters": [], - "parameters": [ - { - "name": "event", - "code": "event: { id: string, window: Window }", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "{ id: string, window: Window }", - "kind": "typeLiteral", - "properties": [ - { - "name": "id", - "code": "string", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "string" - }, - "flags": {} - }, - { - "name": "window", - "code": "Window", - "kind": "property", - "type": { - "type": "reference", - "value": "Window", - "source": "typescript" - }, - "flags": {} - } - ] - } - }, - "kind": "parameter" - } - ], - "returnType": { - "type": "intrinsic", - "value": "void" - }, - "code": "(event: { id: string, window: Window }): void", - "kind": "callSignature" - } - ] - } - }, - "flags": { - "isOptional": true - } - }, - { - "name": "onWillClose", - "code": "(event: { id: string, window: Window }): void", - "kind": "property", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "(event: { id: string, window: Window }): void", - "kind": "typeLiteral", - "signatures": [ - { - "name": "__type", - "typeParameters": [], - "parameters": [ - { - "name": "event", - "code": "event: { id: string, window: Window }", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "{ id: string, window: Window }", - "kind": "typeLiteral", - "properties": [ - { - "name": "id", - "code": "string", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "string" - }, - "flags": {} - }, - { - "name": "window", - "code": "Window", - "kind": "property", - "type": { - "type": "reference", - "value": "Window", - "source": "typescript" - }, - "flags": {} - } - ] - } - }, - "kind": "parameter" - } - ], - "returnType": { - "type": "intrinsic", - "value": "void" - }, - "code": "(event: { id: string, window: Window }): void", - "kind": "callSignature" - } - ] - } - }, - "flags": { - "isOptional": true - } - }, - { - "name": "overridePopoutGroup", - "code": "DockviewGroupPanel", - "kind": "property", - "type": { - "type": "reference", - "value": "DockviewGroupPanel", - "source": "dockview-core" - }, - "flags": { - "isOptional": true - } - }, - { - "name": "popoutUrl", - "code": "string", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "string" - }, - "flags": { - "isOptional": true - } - }, - { - "name": "position", - "code": "Box", - "kind": "property", - "type": { - "type": "reference", - "value": "Box", - "source": "dockview-core" - }, - "flags": { - "isOptional": true - } - } - ] - } + "type": "reference", + "value": "DockviewPopoutGroupOptions", + "source": "dockview-core" }, "kind": "parameter" } @@ -5379,7 +5218,7 @@ } ] }, - "code": "(itemToPopout: DockviewPanel | DockviewGroupPanel, options?: { onDidOpen?: (event: { id: string, window: Window }): void, onWillClose?: (event: { id: string, window: Window }): void, overridePopoutGroup?: DockviewGroupPanel, popoutUrl?: string, position?: Box }): Promise", + "code": "(itemToPopout: DockviewPanel | DockviewGroupPanel, options?: DockviewPopoutGroupOptions): Promise", "kind": "callSignature" } ] @@ -6261,7 +6100,7 @@ }, { "name": "removePanel", - "code": "(panel: IDockviewPanel, options: { removeEmptyGroup: boolean, skipDispose: boolean, skipSetActiveGroup?: boolean }): void", + "code": "(panel: IDockviewPanel, options: { removeEmptyGroup: boolean, skipDispose?: boolean, skipSetActiveGroup?: boolean }): void", "kind": "method", "signature": [ { @@ -6280,12 +6119,12 @@ }, { "name": "options", - "code": "options: { removeEmptyGroup: boolean, skipDispose: boolean, skipSetActiveGroup?: boolean }", + "code": "options: { removeEmptyGroup: boolean, skipDispose?: boolean, skipSetActiveGroup?: boolean }", "type": { "type": "reflection", "value": { "name": "__type", - "code": "{ removeEmptyGroup: boolean, skipDispose: boolean, skipSetActiveGroup?: boolean }", + "code": "{ removeEmptyGroup: boolean, skipDispose?: boolean, skipSetActiveGroup?: boolean }", "kind": "typeLiteral", "properties": [ { @@ -6306,7 +6145,9 @@ "type": "intrinsic", "value": "boolean" }, - "flags": {} + "flags": { + "isOptional": true + } }, { "name": "skipSetActiveGroup", @@ -6330,7 +6171,7 @@ "type": "intrinsic", "value": "void" }, - "code": "(panel: IDockviewPanel, options: { removeEmptyGroup: boolean, skipDispose: boolean, skipSetActiveGroup?: boolean }): void", + "code": "(panel: IDockviewPanel, options: { removeEmptyGroup: boolean, skipDispose?: boolean, skipSetActiveGroup?: boolean }): void", "kind": "callSignature" } ] @@ -7997,6 +7838,30 @@ } } }, + { + "name": "dropTargetContainer", + "code": "DropTargetAnchorContainer | 'null'", + "kind": "accessor", + "value": { + "name": "dropTargetContainer", + "code": "DropTargetAnchorContainer | 'null'", + "kind": "getSignature", + "returnType": { + "type": "or", + "values": [ + { + "type": "reference", + "value": "DropTargetAnchorContainer", + "source": "dockview-core" + }, + { + "type": "literal", + "value": null + } + ] + } + } + }, { "name": "element", "code": "HTMLElement", @@ -9687,7 +9552,9 @@ ] } ], - "extends": [] + "extends": [ + "AcceptableEvent" + ] }, "DockviewWillDropEvent": { "kind": "class", @@ -10030,7 +9897,7 @@ }, { "name": "onDidDrop", - "code": "Event", + "code": "Event", "kind": "property", "type": { "type": "reference", @@ -10039,7 +9906,27 @@ "typeArguments": [ { "type": "reference", - "value": "PaneviewDropEvent", + "value": "PaneviewDidDropEvent", + "source": "dockview-core" + } + ] + }, + "flags": { + "isReadonly": true + } + }, + { + "name": "onUnhandledDragOverEvent", + "code": "Event", + "kind": "property", + "type": { + "type": "reference", + "value": "Event", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "PaneviewDndOverlayEvent", "source": "dockview-core" } ] @@ -10326,7 +10213,7 @@ }, { "name": "getBodyComponent", - "code": "(): IPaneBodyPart", + "code": "(): IPanePart", "kind": "method", "signature": [ { @@ -10335,10 +10222,10 @@ "parameters": [], "returnType": { "type": "reference", - "value": "IPaneBodyPart", + "value": "IPanePart", "source": "dockview-core" }, - "code": "(): IPaneBodyPart", + "code": "(): IPanePart", "kind": "callSignature" } ] @@ -10364,7 +10251,7 @@ }, { "name": "getHeaderComponent", - "code": "(): IPaneHeaderPart", + "code": "(): IPanePart", "kind": "method", "signature": [ { @@ -10373,10 +10260,10 @@ "parameters": [], "returnType": { "type": "reference", - "value": "IPaneHeaderPart", + "value": "IPanePart", "source": "dockview-core" }, - "code": "(): IPaneHeaderPart", + "code": "(): IPanePart", "kind": "callSignature" } ] @@ -10682,7 +10569,7 @@ }, { "name": "onDidMaximizedNodeChange", - "code": "Event", + "code": "Event", "kind": "property", "type": { "type": "reference", @@ -10690,8 +10577,9 @@ "source": "dockview-core", "typeArguments": [ { - "type": "intrinsic", - "value": "void" + "type": "reference", + "value": "MaximizedViewChanged", + "source": "dockview-core" } ] }, @@ -13011,6 +12899,40 @@ "isReadonly": true } }, + { + "name": "onDidMaximizedChange", + "code": "Event>>", + "kind": "property", + "type": { + "type": "reference", + "value": "Event", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "MaximizedChanged", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "GridviewPanel", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "GridviewPanelApiImpl", + "source": "dockview-core" + } + ] + } + ] + } + ] + }, + "flags": { + "isReadonly": true + } + }, { "name": "onDidRemove", "code": "Event>", @@ -13293,27 +13215,6 @@ } } }, - { - "name": "onDidMaximizedGroupChange", - "code": "Event", - "kind": "accessor", - "value": { - "name": "onDidMaximizedGroupChange", - "code": "Event", - "kind": "getSignature", - "returnType": { - "type": "reference", - "value": "Event", - "source": "dockview-core", - "typeArguments": [ - { - "type": "intrinsic", - "value": "void" - } - ] - } - } - }, { "name": "options", "code": "GridviewComponentOptions", @@ -15222,7 +15123,7 @@ }, { "name": "onDidDrop", - "code": "Event", + "code": "Event", "kind": "property", "type": { "type": "reference", @@ -15231,7 +15132,27 @@ "typeArguments": [ { "type": "reference", - "value": "PaneviewDropEvent", + "value": "PaneviewDidDropEvent", + "source": "dockview-core" + } + ] + }, + "flags": { + "isReadonly": true + } + }, + { + "name": "onUnhandledDragOverEvent", + "code": "Event", + "kind": "property", + "type": { + "type": "reference", + "value": "Event", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "PaneviewDndOverlayEvent", "source": "dockview-core" } ] @@ -15518,7 +15439,7 @@ }, { "name": "getBodyComponent", - "code": "(): IPaneBodyPart", + "code": "(): IPanePart", "kind": "method", "signature": [ { @@ -15527,10 +15448,10 @@ "parameters": [], "returnType": { "type": "reference", - "value": "IPaneBodyPart", + "value": "IPanePart", "source": "dockview-core" }, - "code": "(): IPaneBodyPart", + "code": "(): IPanePart", "kind": "callSignature" } ] @@ -15556,7 +15477,7 @@ }, { "name": "getHeaderComponent", - "code": "(): IPaneHeaderPart", + "code": "(): IPanePart", "kind": "method", "signature": [ { @@ -15565,10 +15486,10 @@ "parameters": [], "returnType": { "type": "reference", - "value": "IPaneHeaderPart", + "value": "IPanePart", "source": "dockview-core" }, - "code": "(): IPaneHeaderPart", + "code": "(): IPanePart", "kind": "callSignature" } ] @@ -16545,7 +16466,7 @@ }, { "name": "onDidDrop", - "code": "Event", + "code": "Event", "kind": "accessor", "value": { "name": "onDidDrop", @@ -16557,7 +16478,7 @@ } ] }, - "code": "Event", + "code": "Event", "kind": "getSignature", "returnType": { "type": "reference", @@ -16566,7 +16487,7 @@ "typeArguments": [ { "type": "reference", - "value": "PaneviewDropEvent", + "value": "PaneviewDidDropEvent", "source": "dockview-core" } ] @@ -16709,6 +16630,28 @@ ] } }, + { + "name": "onUnhandledDragOverEvent", + "code": "Event", + "kind": "accessor", + "value": { + "name": "onUnhandledDragOverEvent", + "code": "Event", + "kind": "getSignature", + "returnType": { + "type": "reference", + "value": "Event", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "PaneviewDndOverlayEvent", + "source": "dockview-core" + } + ] + } + } + }, { "name": "panels", "code": "IPaneviewPanel[]", @@ -17341,7 +17284,7 @@ }, { "name": "onDidDrop", - "code": "Event", + "code": "Event", "kind": "property", "type": { "type": "reference", @@ -17350,7 +17293,7 @@ "typeArguments": [ { "type": "reference", - "value": "PaneviewDropEvent", + "value": "PaneviewDidDropEvent", "source": "dockview-core" } ] @@ -17417,6 +17360,26 @@ "isReadonly": true } }, + { + "name": "onUnhandledDragOverEvent", + "code": "Event", + "kind": "property", + "type": { + "type": "reference", + "value": "Event", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "PaneviewDndOverlayEvent", + "source": "dockview-core" + } + ] + }, + "flags": { + "isReadonly": true + } + }, { "name": "disableResizing", "code": "boolean", @@ -18414,7 +18377,7 @@ }, { "name": "getBodyComponent", - "code": "(): IPaneBodyPart", + "code": "(): IPanePart", "kind": "method", "signature": [ { @@ -18423,10 +18386,10 @@ "parameters": [], "returnType": { "type": "reference", - "value": "IPaneBodyPart", + "value": "IPanePart", "source": "dockview-core" }, - "code": "(): IPaneBodyPart", + "code": "(): IPanePart", "kind": "callSignature" } ] @@ -18452,7 +18415,7 @@ }, { "name": "getHeaderComponent", - "code": "(): IPaneHeaderPart", + "code": "(): IPanePart", "kind": "method", "signature": [ { @@ -18461,10 +18424,10 @@ "parameters": [], "returnType": { "type": "reference", - "value": "IPaneHeaderPart", + "value": "IPanePart", "source": "dockview-core" }, - "code": "(): IPaneHeaderPart", + "code": "(): IPanePart", "kind": "callSignature" } ] @@ -18697,6 +18660,130 @@ "BasePanelView" ] }, + "PaneviewUnhandledDragOverEvent": { + "kind": "class", + "name": "PaneviewUnhandledDragOverEvent", + "children": [ + { + "name": "constructor", + "kind": "constructor", + "code": "" + }, + { + "name": "getData", + "code": "(): PaneTransfer | undefined", + "kind": "property", + "type": { + "type": "reflection", + "value": { + "name": "__type", + "code": "(): PaneTransfer | undefined", + "kind": "typeLiteral", + "signatures": [ + { + "name": "__type", + "typeParameters": [], + "parameters": [], + "returnType": { + "type": "or", + "values": [ + { + "type": "reference", + "value": "PaneTransfer", + "source": "dockview-core" + }, + { + "type": "intrinsic", + "value": "undefined" + } + ] + }, + "code": "(): PaneTransfer | undefined", + "kind": "callSignature" + } + ] + } + }, + "flags": { + "isReadonly": true + } + }, + { + "name": "nativeEvent", + "code": "DragEvent", + "kind": "property", + "type": { + "type": "reference", + "value": "DragEvent", + "source": "typescript" + }, + "flags": { + "isReadonly": true + } + }, + { + "name": "panel", + "code": "IPaneviewPanel", + "kind": "property", + "type": { + "type": "reference", + "value": "IPaneviewPanel", + "source": "dockview-core" + }, + "flags": { + "isReadonly": true + } + }, + { + "name": "position", + "code": "Position", + "kind": "property", + "type": { + "type": "reference", + "value": "Position", + "source": "dockview-core" + }, + "flags": { + "isReadonly": true + } + }, + { + "name": "isAccepted", + "code": "boolean", + "kind": "accessor", + "value": { + "name": "isAccepted", + "code": "boolean", + "kind": "getSignature", + "returnType": { + "type": "intrinsic", + "value": "boolean" + } + } + }, + { + "name": "accept", + "code": "(): void", + "kind": "method", + "signature": [ + { + "name": "accept", + "typeParameters": [], + "parameters": [], + "returnType": { + "type": "intrinsic", + "value": "void" + }, + "code": "(): void", + "kind": "callSignature" + } + ] + } + ], + "extends": [ + "AcceptableEvent" + ] + }, "Splitview": { "kind": "class", "name": "Splitview", @@ -21771,26 +21858,6 @@ "kind": "constructor", "code": "" }, - { - "name": "onChanged", - "code": "Event", - "kind": "property", - "type": { - "type": "reference", - "value": "Event", - "source": "dockview-core", - "typeArguments": [ - { - "type": "reference", - "value": "MouseEvent", - "source": "typescript" - } - ] - }, - "flags": { - "isReadonly": true - } - }, { "name": "onDragStart", "code": "Event", @@ -21831,6 +21898,26 @@ "isReadonly": true } }, + { + "name": "onPointerDown", + "code": "Event", + "kind": "property", + "type": { + "type": "reference", + "value": "Event", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "MouseEvent", + "source": "typescript" + } + ] + }, + "flags": { + "isReadonly": true + } + }, { "name": "onWillShowOverlay", "code": "Event", @@ -23217,7 +23304,9 @@ "type": "intrinsic", "value": "boolean" }, - "flags": {} + "flags": { + "isReadonly": true + } }, { "name": "nativeEvent", @@ -23271,7 +23360,9 @@ ] } ], - "extends": [] + "extends": [ + "IAcceptableEvent" + ] }, "DockviewFrameworkOptions": { "kind": "interface", @@ -24313,6 +24404,34 @@ ], "extends": [] }, + "DockviewMaximizedGroupChanged": { + "kind": "interface", + "name": "DockviewMaximizedGroupChanged", + "children": [ + { + "name": "group", + "code": "DockviewGroupPanel", + "kind": "property", + "type": { + "type": "reference", + "value": "DockviewGroupPanel", + "source": "dockview-core" + }, + "flags": {} + }, + { + "name": "isMaximized", + "code": "boolean", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "boolean" + }, + "flags": {} + } + ], + "extends": [] + }, "DockviewOptions": { "kind": "interface", "name": "DockviewOptions", @@ -24422,6 +24541,40 @@ "isOptional": true } }, + { + "name": "disableTabsOverflowList", + "code": "boolean", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "boolean" + }, + "flags": { + "isOptional": true + } + }, + { + "name": "dndEdges", + "code": "DroptargetOverlayModel | 'false'", + "kind": "property", + "type": { + "type": "or", + "values": [ + { + "type": "reference", + "value": "DroptargetOverlayModel", + "source": "dockview-core" + }, + { + "type": "literal", + "value": false + } + ] + }, + "flags": { + "isOptional": true + } + }, { "name": "floatingGroupBounds", "code": "{ minimumHeightWithinViewport?: number, minimumWidthWithinViewport?: number } | 'boundedWithinViewport'", @@ -24473,26 +24626,6 @@ "isOptional": true } }, - { - "name": "gap", - "code": "number", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "number" - }, - "flags": { - "isOptional": true - }, - "comment": { - "summary": [ - { - "kind": "text", - "text": "Pixel gap between groups" - } - ] - } - }, { "name": "hideBorders", "code": "boolean", @@ -24517,6 +24650,43 @@ "isOptional": true } }, + { + "name": "noPanelsOverlay", + "code": "'watermark' | 'emptyGroup'", + "kind": "property", + "type": { + "type": "or", + "values": [ + { + "type": "literal", + "value": "watermark" + }, + { + "type": "literal", + "value": "emptyGroup" + } + ] + }, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "Define the behaviour of the dock when there are no panels to display. Defaults to " + }, + { + "kind": "code", + "text": "`watermark`" + }, + { + "kind": "text", + "text": "." + } + ] + } + }, { "name": "popoutUrl", "code": "string", @@ -24540,6 +24710,28 @@ }, "flags": { "isOptional": true + }, + "comment": { + "summary": [], + "blockTags": [ + { + "tag": "@deprecated", + "content": [ + { + "kind": "text", + "text": "use " + }, + { + "kind": "code", + "text": "`dndEdges`" + }, + { + "kind": "text", + "text": " instead. To be removed in a future version." + } + ] + } + ] } }, { @@ -24562,6 +24754,19 @@ "flags": { "isOptional": true } + }, + { + "name": "theme", + "code": "DockviewTheme", + "kind": "property", + "type": { + "type": "reference", + "value": "DockviewTheme", + "source": "dockview-core" + }, + "flags": { + "isOptional": true + } } ], "extends": [] @@ -25333,6 +25538,207 @@ ], "extends": [] }, + "DockviewPopoutGroupOptions": { + "kind": "interface", + "name": "DockviewPopoutGroupOptions", + "children": [ + { + "name": "onDidOpen", + "code": "(event: { id: string, window: Window }): void", + "kind": "property", + "type": { + "type": "reflection", + "value": { + "name": "__type", + "code": "(event: { id: string, window: Window }): void", + "kind": "typeLiteral", + "signatures": [ + { + "name": "__type", + "typeParameters": [], + "parameters": [ + { + "name": "event", + "code": "event: { id: string, window: Window }", + "type": { + "type": "reflection", + "value": { + "name": "__type", + "code": "{ id: string, window: Window }", + "kind": "typeLiteral", + "properties": [ + { + "name": "id", + "code": "string", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "string" + }, + "flags": {} + }, + { + "name": "window", + "code": "Window", + "kind": "property", + "type": { + "type": "reference", + "value": "Window", + "source": "typescript" + }, + "flags": {} + } + ] + } + }, + "kind": "parameter" + } + ], + "returnType": { + "type": "intrinsic", + "value": "void" + }, + "code": "(event: { id: string, window: Window }): void", + "kind": "callSignature" + } + ] + } + }, + "flags": { + "isOptional": true + } + }, + { + "name": "onWillClose", + "code": "(event: { id: string, window: Window }): void", + "kind": "property", + "type": { + "type": "reflection", + "value": { + "name": "__type", + "code": "(event: { id: string, window: Window }): void", + "kind": "typeLiteral", + "signatures": [ + { + "name": "__type", + "typeParameters": [], + "parameters": [ + { + "name": "event", + "code": "event: { id: string, window: Window }", + "type": { + "type": "reflection", + "value": { + "name": "__type", + "code": "{ id: string, window: Window }", + "kind": "typeLiteral", + "properties": [ + { + "name": "id", + "code": "string", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "string" + }, + "flags": {} + }, + { + "name": "window", + "code": "Window", + "kind": "property", + "type": { + "type": "reference", + "value": "Window", + "source": "typescript" + }, + "flags": {} + } + ] + } + }, + "kind": "parameter" + } + ], + "returnType": { + "type": "intrinsic", + "value": "void" + }, + "code": "(event: { id: string, window: Window }): void", + "kind": "callSignature" + } + ] + } + }, + "flags": { + "isOptional": true + } + }, + { + "name": "overridePopoutGroup", + "code": "DockviewGroupPanel", + "kind": "property", + "type": { + "type": "reference", + "value": "DockviewGroupPanel", + "source": "dockview-core" + }, + "flags": { + "isOptional": true + } + }, + { + "name": "popoutUrl", + "code": "string", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "string" + }, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The same-origin path at which the popout window will be created\r\n\r\nDefaults to " + }, + { + "kind": "code", + "text": "`/popout.html`" + }, + { + "kind": "text", + "text": " if not provided" + } + ] + } + }, + { + "name": "position", + "code": "Box", + "kind": "property", + "type": { + "type": "reference", + "value": "Box", + "source": "dockview-core" + }, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The position of the popout group" + } + ] + } + } + ], + "extends": [] + }, "DockviewReadyEvent": { "kind": "interface", "name": "DockviewReadyEvent", @@ -25351,6 +25757,143 @@ ], "extends": [] }, + "DockviewTheme": { + "kind": "interface", + "name": "DockviewTheme", + "children": [ + { + "name": "className", + "code": "string", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "string" + }, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The class name to apply to the theme containing the CSS variables settings." + } + ] + } + }, + { + "name": "dndOverlayMounting", + "code": "'relative' | 'absolute'", + "kind": "property", + "type": { + "type": "or", + "values": [ + { + "type": "literal", + "value": "relative" + }, + { + "type": "literal", + "value": "absolute" + } + ] + }, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The mouting position of the overlay shown when dragging a panel. " + }, + { + "kind": "code", + "text": "`absolute`" + }, + { + "kind": "text", + "text": "\nwill mount the overlay to root of the dockview component whereas " + }, + { + "kind": "code", + "text": "`relative`" + }, + { + "kind": "text", + "text": " will mount the overlay to the group container." + } + ] + } + }, + { + "name": "dndPanelOverlay", + "code": "'content' | 'group'", + "kind": "property", + "type": { + "type": "or", + "values": [ + { + "type": "literal", + "value": "content" + }, + { + "type": "literal", + "value": "group" + } + ] + }, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "When dragging a panel, the overlay can either encompass the panel contents or the entire group including the tab header space." + } + ] + } + }, + { + "name": "gap", + "code": "number", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "number" + }, + "flags": { + "isOptional": true + }, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The gap between the groups" + } + ] + } + }, + { + "name": "name", + "code": "string", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "string" + }, + "flags": {}, + "comment": { + "summary": [ + { + "kind": "text", + "text": "The name of the theme" + } + ] + } + } + ], + "extends": [] + }, "ExpansionEvent": { "kind": "interface", "name": "ExpansionEvent", @@ -25946,126 +26489,55 @@ "BasePanelViewState" ] }, - "GridviewComponentOptions": { + "GridviewFrameworkOptions": { "kind": "interface", - "name": "GridviewComponentOptions", + "name": "GridviewFrameworkOptions", "children": [ { - "name": "className", - "code": "string", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "string" - }, - "flags": { - "isOptional": true - } - }, - { - "name": "components", - "code": "", + "name": "createComponent", + "code": "(options: CreateComponentOptions): GridviewPanel", "kind": "property", "type": { "type": "reflection", "value": { "name": "__type", - "code": "", - "kind": "typeLiteral" - } - }, - "flags": { - "isOptional": true - } - }, - { - "name": "disableAutoResizing", - "code": "boolean", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "boolean" - }, - "flags": { - "isOptional": true - } - }, - { - "name": "frameworkComponentFactory", - "code": "FrameworkFactory>", - "kind": "property", - "type": { - "type": "reference", - "value": "FrameworkFactory", - "source": "dockview-core", - "typeArguments": [ - { - "type": "reference", - "value": "GridviewPanel", - "source": "dockview-core", - "typeArguments": [ - { + "code": "(options: CreateComponentOptions): GridviewPanel", + "kind": "typeLiteral", + "signatures": [ + { + "name": "__type", + "typeParameters": [], + "parameters": [ + { + "name": "options", + "code": "options: CreateComponentOptions", + "type": { + "type": "reference", + "value": "CreateComponentOptions", + "source": "dockview-core" + }, + "kind": "parameter" + } + ], + "returnType": { "type": "reference", - "value": "GridviewPanelApiImpl", - "source": "dockview-core" - } - ] - } - ] - }, - "flags": { - "isOptional": true - } - }, - { - "name": "frameworkComponents", - "code": "", - "kind": "property", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "", - "kind": "typeLiteral" + "value": "GridviewPanel", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "GridviewPanelApiImpl", + "source": "dockview-core" + } + ] + }, + "code": "(options: CreateComponentOptions): GridviewPanel", + "kind": "callSignature" + } + ] } }, - "flags": { - "isOptional": true - } - }, - { - "name": "orientation", - "code": "Orientation", - "kind": "property", - "type": { - "type": "reference", - "value": "Orientation", - "source": "dockview-core" - }, "flags": {} - }, - { - "name": "proportionalLayout", - "code": "boolean", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "boolean" - }, - "flags": {} - }, - { - "name": "styles", - "code": "ISplitviewStyles", - "kind": "property", - "type": { - "type": "reference", - "value": "ISplitviewStyles", - "source": "dockview-core" - }, - "flags": { - "isOptional": true - } } ], "extends": [] @@ -26208,6 +26680,72 @@ "PanelInitParameters" ] }, + "GridviewOptions": { + "kind": "interface", + "name": "GridviewOptions", + "children": [ + { + "name": "className", + "code": "string", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "string" + }, + "flags": { + "isOptional": true + } + }, + { + "name": "disableAutoResizing", + "code": "boolean", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "boolean" + }, + "flags": { + "isOptional": true + } + }, + { + "name": "hideBorders", + "code": "boolean", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "boolean" + }, + "flags": { + "isOptional": true + } + }, + { + "name": "orientation", + "code": "Orientation", + "kind": "property", + "type": { + "type": "reference", + "value": "Orientation", + "source": "dockview-core" + }, + "flags": {} + }, + { + "name": "proportionalLayout", + "code": "boolean", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "boolean" + }, + "flags": { + "isOptional": true + } + } + ], + "extends": [] + }, "GridviewPanelApi": { "kind": "interface", "name": "GridviewPanelApi", @@ -27325,8 +27863,8 @@ } }, { - "name": "onDidMaximizedGroupChange", - "code": "Event", + "name": "onDidMaximizedChange", + "code": "Event>", "kind": "property", "type": { "type": "reference", @@ -27334,8 +27872,17 @@ "source": "dockview-core", "typeArguments": [ { - "type": "intrinsic", - "value": "void" + "type": "reference", + "value": "MaximizedChanged", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "T", + "source": "dockview-core", + "refersToTypeParameter": true + } + ] } ] }, @@ -28354,18 +28901,6 @@ "isReadonly": true } }, - { - "name": "gap", - "code": "number", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "number" - }, - "flags": { - "isReadonly": true - } - }, { "name": "getGroupPanel", "code": "(id: string): IDockviewPanel | undefined", @@ -28658,8 +29193,8 @@ } }, { - "name": "onDidMaximizedGroupChange", - "code": "Event", + "name": "onDidMaximizedChange", + "code": "Event>", "kind": "property", "type": { "type": "reference", @@ -28667,8 +29202,36 @@ "source": "dockview-core", "typeArguments": [ { - "type": "intrinsic", - "value": "void" + "type": "reference", + "value": "MaximizedChanged", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "DockviewGroupPanel", + "source": "dockview-core" + } + ] + } + ] + }, + "flags": { + "isReadonly": true + } + }, + { + "name": "onDidMaximizedGroupChange", + "code": "Event", + "kind": "property", + "type": { + "type": "reference", + "value": "Event", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "DockviewMaximizedGroupChanged", + "source": "dockview-core" } ] }, @@ -28954,7 +29517,7 @@ }, { "name": "addFloatingGroup", - "code": "(item: IDockviewPanel | DockviewGroupPanel, options?: FloatingGroupOptions): void", + "code": "(item: DockviewGroupPanel | IDockviewPanel, options?: FloatingGroupOptions): void", "kind": "method", "signature": [ { @@ -28963,18 +29526,18 @@ "parameters": [ { "name": "item", - "code": "item: IDockviewPanel | DockviewGroupPanel", + "code": "item: DockviewGroupPanel | IDockviewPanel", "type": { "type": "or", "values": [ { "type": "reference", - "value": "IDockviewPanel", + "value": "DockviewGroupPanel", "source": "dockview-core" }, { "type": "reference", - "value": "DockviewGroupPanel", + "value": "IDockviewPanel", "source": "dockview-core" } ] @@ -28996,14 +29559,14 @@ "type": "intrinsic", "value": "void" }, - "code": "(item: IDockviewPanel | DockviewGroupPanel, options?: FloatingGroupOptions): void", + "code": "(item: DockviewGroupPanel | IDockviewPanel, options?: FloatingGroupOptions): void", "kind": "callSignature" } ] }, { "name": "addGroup", - "code": "(options?: GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup | GroupOptions & AbsolutePosition): DockviewGroupPanel", + "code": "(options?: GroupOptions & AbsolutePosition | GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup): DockviewGroupPanel", "kind": "method", "signature": [ { @@ -29012,10 +29575,25 @@ "parameters": [ { "name": "options", - "code": "options?: GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup | GroupOptions & AbsolutePosition", + "code": "options?: GroupOptions & AbsolutePosition | GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup", "type": { "type": "or", "values": [ + { + "type": "intersection", + "values": [ + { + "type": "reference", + "value": "GroupOptions", + "source": "dockview-core" + }, + { + "type": "reference", + "value": "AbsolutePosition", + "source": "dockview-core" + } + ] + }, { "type": "intersection", "values": [ @@ -29045,21 +29623,6 @@ "source": "dockview-core" } ] - }, - { - "type": "intersection", - "values": [ - { - "type": "reference", - "value": "GroupOptions", - "source": "dockview-core" - }, - { - "type": "reference", - "value": "AbsolutePosition", - "source": "dockview-core" - } - ] } ] }, @@ -29071,7 +29634,7 @@ "value": "DockviewGroupPanel", "source": "dockview-core" }, - "code": "(options?: GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup | GroupOptions & AbsolutePosition): DockviewGroupPanel", + "code": "(options?: GroupOptions & AbsolutePosition | GroupOptions & AddGroupOptionsWithPanel | GroupOptions & AddGroupOptionsWithGroup): DockviewGroupPanel", "kind": "callSignature" } ] @@ -29125,7 +29688,7 @@ }, { "name": "addPopoutGroup", - "code": "(item: IDockviewPanel | DockviewGroupPanel, options?: { onDidOpen?: (event: { id: string, window: Window }): void, onWillClose?: (event: { id: string, window: Window }): void, popoutUrl?: string, position?: Box }): Promise", + "code": "(item: DockviewGroupPanel | IDockviewPanel, options?: { onDidOpen?: (event: { id: string, window: Window }): void, onWillClose?: (event: { id: string, window: Window }): void, popoutUrl?: string, position?: Box }): Promise", "kind": "method", "signature": [ { @@ -29134,18 +29697,18 @@ "parameters": [ { "name": "item", - "code": "item: IDockviewPanel | DockviewGroupPanel", + "code": "item: DockviewGroupPanel | IDockviewPanel", "type": { "type": "or", "values": [ { "type": "reference", - "value": "IDockviewPanel", + "value": "DockviewGroupPanel", "source": "dockview-core" }, { "type": "reference", - "value": "DockviewGroupPanel", + "value": "IDockviewPanel", "source": "dockview-core" } ] @@ -29336,7 +29899,7 @@ } ] }, - "code": "(item: IDockviewPanel | DockviewGroupPanel, options?: { onDidOpen?: (event: { id: string, window: Window }): void, onWillClose?: (event: { id: string, window: Window }): void, popoutUrl?: string, position?: Box }): Promise", + "code": "(item: DockviewGroupPanel | IDockviewPanel, options?: { onDidOpen?: (event: { id: string, window: Window }): void, onWillClose?: (event: { id: string, window: Window }): void, popoutUrl?: string, position?: Box }): Promise", "kind": "callSignature" } ] @@ -33038,8 +33601,8 @@ } }, { - "name": "onDidMaximizedGroupChange", - "code": "Event", + "name": "onDidMaximizedChange", + "code": "Event>>", "kind": "property", "type": { "type": "reference", @@ -33047,8 +33610,23 @@ "source": "dockview-core", "typeArguments": [ { - "type": "intrinsic", - "value": "void" + "type": "reference", + "value": "MaximizedChanged", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "GridviewPanel", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "GridviewPanelApiImpl", + "source": "dockview-core" + } + ] + } + ] } ] }, @@ -34253,114 +34831,9 @@ ], "extends": [] }, - "IPaneBodyPart": { + "IPanePart": { "kind": "interface", - "name": "IPaneBodyPart", - "children": [ - { - "name": "element", - "code": "HTMLElement", - "kind": "property", - "type": { - "type": "reference", - "value": "HTMLElement", - "source": "typescript" - }, - "flags": { - "isReadonly": true - } - }, - { - "name": "dispose", - "code": "(): void", - "kind": "method", - "signature": [ - { - "name": "dispose", - "typeParameters": [], - "parameters": [], - "returnType": { - "type": "intrinsic", - "value": "void" - }, - "code": "(): void", - "kind": "callSignature" - } - ] - }, - { - "name": "init", - "code": "(parameters: PanePanelComponentInitParameter): void", - "kind": "method", - "signature": [ - { - "name": "init", - "typeParameters": [], - "parameters": [ - { - "name": "parameters", - "code": "parameters: PanePanelComponentInitParameter", - "type": { - "type": "reference", - "value": "PanePanelComponentInitParameter", - "source": "dockview-core" - }, - "kind": "parameter" - } - ], - "returnType": { - "type": "intrinsic", - "value": "void" - }, - "code": "(parameters: PanePanelComponentInitParameter): void", - "kind": "callSignature" - } - ] - }, - { - "name": "update", - "code": "(params: PanelUpdateEvent): void", - "kind": "method", - "signature": [ - { - "name": "update", - "typeParameters": [], - "parameters": [ - { - "name": "params", - "code": "params: PanelUpdateEvent", - "type": { - "type": "reference", - "value": "PanelUpdateEvent", - "source": "dockview-core", - "typeArguments": [ - { - "type": "reference", - "value": "Parameters", - "source": "dockview-core" - } - ] - }, - "kind": "parameter" - } - ], - "returnType": { - "type": "intrinsic", - "value": "void" - }, - "code": "(params: PanelUpdateEvent): void", - "kind": "callSignature" - } - ] - } - ], - "extends": [ - "IDisposable" - ] - }, - "IPaneHeaderPart": { - "kind": "interface", - "name": "IPaneHeaderPart", + "name": "IPanePart", "children": [ { "name": "element", @@ -35002,7 +35475,7 @@ }, { "name": "onDidDrop", - "code": "Event", + "code": "Event", "kind": "property", "type": { "type": "reference", @@ -35011,7 +35484,7 @@ "typeArguments": [ { "type": "reference", - "value": "PaneviewDropEvent", + "value": "PaneviewDidDropEvent", "source": "dockview-core" } ] @@ -35078,6 +35551,26 @@ "isReadonly": true } }, + { + "name": "onUnhandledDragOverEvent", + "code": "Event", + "kind": "property", + "type": { + "type": "reference", + "value": "Event", + "source": "dockview-core", + "typeArguments": [ + { + "type": "reference", + "value": "PaneviewDndOverlayEvent", + "source": "dockview-core" + } + ] + }, + "flags": { + "isReadonly": true + } + }, { "name": "options", "code": "PaneviewComponentOptions", @@ -36773,7 +37266,7 @@ }, { "name": "init", - "code": "(parameters: GroupPanelPartInitParameters): void", + "code": "(parameters: TabPartInitParameters): void", "kind": "method", "signature": [ { @@ -36782,10 +37275,10 @@ "parameters": [ { "name": "parameters", - "code": "parameters: GroupPanelPartInitParameters", + "code": "parameters: TabPartInitParameters", "type": { "type": "reference", - "value": "GroupPanelPartInitParameters", + "value": "TabPartInitParameters", "source": "dockview-core" }, "kind": "parameter" @@ -36795,7 +37288,7 @@ "type": "intrinsic", "value": "void" }, - "code": "(parameters: GroupPanelPartInitParameters): void", + "code": "(parameters: TabPartInitParameters): void", "kind": "callSignature" } ] @@ -37421,6 +37914,63 @@ "Optional" ] }, + "MaximizedChanged": { + "kind": "interface", + "name": "MaximizedChanged", + "children": [ + { + "name": "isMaximized", + "code": "boolean", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "boolean" + }, + "flags": {} + }, + { + "name": "panel", + "code": "MaximizedChanged.T", + "kind": "property", + "type": { + "type": "reference", + "value": "T", + "source": "dockview-core", + "refersToTypeParameter": true + }, + "flags": {} + } + ], + "extends": [] + }, + "MaximizedViewChanged": { + "kind": "interface", + "name": "MaximizedViewChanged", + "children": [ + { + "name": "isMaximized", + "code": "boolean", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "boolean" + }, + "flags": {} + }, + { + "name": "view", + "code": "IGridView", + "kind": "property", + "type": { + "type": "reference", + "value": "IGridView", + "source": "dockview-core" + }, + "flags": {} + } + ], + "extends": [] + }, "MovePanelEvent": { "kind": "interface", "name": "MovePanelEvent", @@ -38510,207 +39060,6 @@ "PanelInitParameters" ] }, - "PaneviewComponentOptions": { - "kind": "interface", - "name": "PaneviewComponentOptions", - "children": [ - { - "name": "className", - "code": "string", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "string" - }, - "flags": { - "isOptional": true - } - }, - { - "name": "components", - "code": "", - "kind": "property", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "", - "kind": "typeLiteral" - } - }, - "flags": { - "isOptional": true - } - }, - { - "name": "disableAutoResizing", - "code": "boolean", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "boolean" - }, - "flags": { - "isOptional": true - } - }, - { - "name": "disableDnd", - "code": "boolean", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "boolean" - }, - "flags": { - "isOptional": true - } - }, - { - "name": "frameworkComponents", - "code": "", - "kind": "property", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "", - "kind": "typeLiteral" - } - }, - "flags": { - "isOptional": true - } - }, - { - "name": "frameworkWrapper", - "code": "{ body: FrameworkFactory, header: FrameworkFactory }", - "kind": "property", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "{ body: FrameworkFactory, header: FrameworkFactory }", - "kind": "typeLiteral", - "properties": [ - { - "name": "body", - "code": "FrameworkFactory", - "kind": "property", - "type": { - "type": "reference", - "value": "FrameworkFactory", - "source": "dockview-core", - "typeArguments": [ - { - "type": "reference", - "value": "IPaneBodyPart", - "source": "dockview-core" - } - ] - }, - "flags": {} - }, - { - "name": "header", - "code": "FrameworkFactory", - "kind": "property", - "type": { - "type": "reference", - "value": "FrameworkFactory", - "source": "dockview-core", - "typeArguments": [ - { - "type": "reference", - "value": "IPaneHeaderPart", - "source": "dockview-core" - } - ] - }, - "flags": {} - } - ] - } - }, - "flags": { - "isOptional": true - } - }, - { - "name": "headerComponents", - "code": "", - "kind": "property", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "", - "kind": "typeLiteral" - } - }, - "flags": { - "isOptional": true - } - }, - { - "name": "headerframeworkComponents", - "code": "", - "kind": "property", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "", - "kind": "typeLiteral" - } - }, - "flags": { - "isOptional": true - } - }, - { - "name": "showDndOverlay", - "code": "(event: PaneviewDndOverlayEvent): boolean", - "kind": "property", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "(event: PaneviewDndOverlayEvent): boolean", - "kind": "typeLiteral", - "signatures": [ - { - "name": "__type", - "typeParameters": [], - "parameters": [ - { - "name": "event", - "code": "event: PaneviewDndOverlayEvent", - "type": { - "type": "reference", - "value": "PaneviewDndOverlayEvent", - "source": "dockview-core" - }, - "kind": "parameter" - } - ], - "returnType": { - "type": "intrinsic", - "value": "boolean" - }, - "code": "(event: PaneviewDndOverlayEvent): boolean", - "kind": "callSignature" - } - ] - } - }, - "flags": { - "isOptional": true - } - } - ], - "extends": [] - }, "PaneviewDndOverlayEvent": { "kind": "interface", "name": "PaneviewDndOverlayEvent", @@ -38752,6 +39101,18 @@ }, "flags": {} }, + { + "name": "isAccepted", + "code": "boolean", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "boolean" + }, + "flags": { + "isReadonly": true + } + }, { "name": "nativeEvent", "code": "DragEvent", @@ -38773,9 +39134,40 @@ "source": "dockview-core" }, "flags": {} + }, + { + "name": "position", + "code": "Position", + "kind": "property", + "type": { + "type": "reference", + "value": "Position", + "source": "dockview-core" + }, + "flags": {} + }, + { + "name": "accept", + "code": "(): void", + "kind": "method", + "signature": [ + { + "name": "accept", + "typeParameters": [], + "parameters": [], + "returnType": { + "type": "intrinsic", + "value": "void" + }, + "code": "(): void", + "kind": "callSignature" + } + ] } ], - "extends": [] + "extends": [ + "IAcceptableEvent" + ] }, "PaneviewDropEvent": { "kind": "interface", @@ -38871,6 +39263,145 @@ "DroptargetEvent" ] }, + "PaneviewFrameworkOptions": { + "kind": "interface", + "name": "PaneviewFrameworkOptions", + "children": [ + { + "name": "createComponent", + "code": "(options: CreateComponentOptions): IPanePart", + "kind": "property", + "type": { + "type": "reflection", + "value": { + "name": "__type", + "code": "(options: CreateComponentOptions): IPanePart", + "kind": "typeLiteral", + "signatures": [ + { + "name": "__type", + "typeParameters": [], + "parameters": [ + { + "name": "options", + "code": "options: CreateComponentOptions", + "type": { + "type": "reference", + "value": "CreateComponentOptions", + "source": "dockview-core" + }, + "kind": "parameter" + } + ], + "returnType": { + "type": "reference", + "value": "IPanePart", + "source": "dockview-core" + }, + "code": "(options: CreateComponentOptions): IPanePart", + "kind": "callSignature" + } + ] + } + }, + "flags": {} + }, + { + "name": "createHeaderComponent", + "code": "(options: CreateComponentOptions): IPanePart | undefined", + "kind": "property", + "type": { + "type": "reflection", + "value": { + "name": "__type", + "code": "(options: CreateComponentOptions): IPanePart | undefined", + "kind": "typeLiteral", + "signatures": [ + { + "name": "__type", + "typeParameters": [], + "parameters": [ + { + "name": "options", + "code": "options: CreateComponentOptions", + "type": { + "type": "reference", + "value": "CreateComponentOptions", + "source": "dockview-core" + }, + "kind": "parameter" + } + ], + "returnType": { + "type": "or", + "values": [ + { + "type": "reference", + "value": "IPanePart", + "source": "dockview-core" + }, + { + "type": "intrinsic", + "value": "undefined" + } + ] + }, + "code": "(options: CreateComponentOptions): IPanePart | undefined", + "kind": "callSignature" + } + ] + } + }, + "flags": { + "isOptional": true + } + } + ], + "extends": [] + }, + "PaneviewOptions": { + "kind": "interface", + "name": "PaneviewOptions", + "children": [ + { + "name": "className", + "code": "string", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "string" + }, + "flags": { + "isOptional": true + } + }, + { + "name": "disableAutoResizing", + "code": "boolean", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "boolean" + }, + "flags": { + "isOptional": true + } + }, + { + "name": "disableDnd", + "code": "boolean", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "boolean" + }, + "flags": { + "isOptional": true + } + } + ], + "extends": [] + }, "PaneviewPanelApi": { "kind": "interface", "name": "PaneviewPanelApi", @@ -39720,6 +40251,19 @@ }, "flags": {} }, + { + "name": "maximizedNode", + "code": "SerializedNodeDescriptor", + "kind": "property", + "type": { + "type": "reference", + "value": "SerializedNodeDescriptor", + "source": "dockview-core" + }, + "flags": { + "isOptional": true + } + }, { "name": "orientation", "code": "Orientation", @@ -39800,6 +40344,26 @@ ], "extends": [] }, + "SerializedNodeDescriptor": { + "kind": "interface", + "name": "SerializedNodeDescriptor", + "children": [ + { + "name": "location", + "code": "number[]", + "kind": "property", + "type": { + "type": "array", + "value": { + "type": "intrinsic", + "value": "number" + } + }, + "flags": {} + } + ], + "extends": [] + }, "SerializedPaneview": { "kind": "interface", "name": "SerializedPaneview", @@ -40044,6 +40608,18 @@ ] }, "flags": {} + }, + { + "name": "url", + "code": "string", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "string" + }, + "flags": { + "isOptional": true + } } ], "extends": [] @@ -40284,8 +40860,7 @@ "source": "dockview-core" }, "flags": { - "isOptional": true, - "isReadonly": true + "isOptional": true } }, { @@ -40297,21 +40872,30 @@ "value": "number" }, "flags": { - "isOptional": true, - "isReadonly": true + "isOptional": true } }, { "name": "orientation", - "code": "Orientation", + "code": "Orientation.VERTICAL | Orientation.HORIZONTAL", "kind": "property", "type": { - "type": "reference", - "value": "Orientation", - "source": "dockview-core" + "type": "or", + "values": [ + { + "type": "reference", + "value": "Orientation.VERTICAL", + "source": "dockview-core" + }, + { + "type": "reference", + "value": "Orientation.HORIZONTAL", + "source": "dockview-core" + } + ] }, "flags": { - "isReadonly": true + "isOptional": true } }, { @@ -40323,8 +40907,7 @@ "value": "boolean" }, "flags": { - "isOptional": true, - "isReadonly": true + "isOptional": true } }, { @@ -40337,16 +40920,61 @@ "source": "dockview-core" }, "flags": { - "isOptional": true, - "isReadonly": true + "isOptional": true } } ], "extends": [] }, - "SplitviewComponentOptions": { + "SplitviewFrameworkOptions": { "kind": "interface", - "name": "SplitviewComponentOptions", + "name": "SplitviewFrameworkOptions", + "children": [ + { + "name": "createComponent", + "code": "(options: CreateComponentOptions): SplitviewPanel", + "kind": "property", + "type": { + "type": "reflection", + "value": { + "name": "__type", + "code": "(options: CreateComponentOptions): SplitviewPanel", + "kind": "typeLiteral", + "signatures": [ + { + "name": "__type", + "typeParameters": [], + "parameters": [ + { + "name": "options", + "code": "options: CreateComponentOptions", + "type": { + "type": "reference", + "value": "CreateComponentOptions", + "source": "dockview-core" + }, + "kind": "parameter" + } + ], + "returnType": { + "type": "reference", + "value": "SplitviewPanel", + "source": "dockview-core" + }, + "code": "(options: CreateComponentOptions): SplitviewPanel", + "kind": "callSignature" + } + ] + } + }, + "flags": {} + } + ], + "extends": [] + }, + "SplitviewOptions": { + "kind": "interface", + "name": "SplitviewOptions", "children": [ { "name": "className", @@ -40360,22 +40988,6 @@ "isOptional": true } }, - { - "name": "components", - "code": "", - "kind": "property", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "", - "kind": "typeLiteral" - } - }, - "flags": { - "isOptional": true - } - }, { "name": "descriptor", "code": "ISplitViewDescriptor", @@ -40386,8 +40998,7 @@ "source": "dockview-core" }, "flags": { - "isOptional": true, - "isReadonly": true + "isOptional": true } }, { @@ -40402,42 +41013,6 @@ "isOptional": true } }, - { - "name": "frameworkComponents", - "code": "", - "kind": "property", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "", - "kind": "typeLiteral" - } - }, - "flags": { - "isOptional": true - } - }, - { - "name": "frameworkWrapper", - "code": "FrameworkFactory", - "kind": "property", - "type": { - "type": "reference", - "value": "FrameworkFactory", - "source": "dockview-core", - "typeArguments": [ - { - "type": "reference", - "value": "SplitviewPanel", - "source": "dockview-core" - } - ] - }, - "flags": { - "isOptional": true - } - }, { "name": "margin", "code": "number", @@ -40447,21 +41022,30 @@ "value": "number" }, "flags": { - "isOptional": true, - "isReadonly": true + "isOptional": true } }, { "name": "orientation", - "code": "Orientation", + "code": "Orientation.VERTICAL | Orientation.HORIZONTAL", "kind": "property", "type": { - "type": "reference", - "value": "Orientation", - "source": "dockview-core" + "type": "or", + "values": [ + { + "type": "reference", + "value": "Orientation.VERTICAL", + "source": "dockview-core" + }, + { + "type": "reference", + "value": "Orientation.HORIZONTAL", + "source": "dockview-core" + } + ] }, "flags": { - "isReadonly": true + "isOptional": true } }, { @@ -40473,8 +41057,7 @@ "value": "boolean" }, "flags": { - "isOptional": true, - "isReadonly": true + "isOptional": true } }, { @@ -40487,8 +41070,7 @@ "source": "dockview-core" }, "flags": { - "isOptional": true, - "isReadonly": true + "isOptional": true } } ], @@ -41021,6 +41603,69 @@ ], "extends": [] }, + "TabPartInitParameters": { + "kind": "interface", + "name": "TabPartInitParameters", + "children": [ + { + "name": "api", + "code": "DockviewPanelApi", + "kind": "property", + "type": { + "type": "reference", + "value": "DockviewPanelApi", + "source": "dockview-core" + }, + "flags": {} + }, + { + "name": "containerApi", + "code": "DockviewApi", + "kind": "property", + "type": { + "type": "reference", + "value": "DockviewApi", + "source": "dockview-core" + }, + "flags": {} + }, + { + "name": "params", + "code": "Parameters", + "kind": "property", + "type": { + "type": "reference", + "value": "Parameters", + "source": "dockview-core" + }, + "flags": {} + }, + { + "name": "tabLocation", + "code": "TabLocation", + "kind": "property", + "type": { + "type": "reference", + "value": "TabLocation", + "source": "dockview-core" + }, + "flags": {} + }, + { + "name": "title", + "code": "string", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "string" + }, + "flags": {} + } + ], + "extends": [ + "GroupPanelPartInitParameters" + ] + }, "TitleEvent": { "kind": "interface", "name": "TitleEvent", @@ -41391,6 +42036,14 @@ }, "Direction": { "name": "Direction", + "comment": { + "summary": [ + { + "kind": "text", + "text": "A direction in which a panel can be moved or placed relative to another panel." + } + ] + }, "code": "'within' | 'below' | 'above' | 'right' | 'left'", "typeParameters": [], "type": { @@ -41496,7 +42149,7 @@ }, "DockviewGroupLocation": { "name": "DockviewGroupLocation", - "code": "{ getWindow: (): Window, type: 'popout' } | { type: 'floating' } | { type: 'grid' }", + "code": "{ getWindow: (): Window, popoutUrl?: string, type: 'popout' } | { type: 'floating' } | { type: 'grid' }", "typeParameters": [], "type": { "type": "or", @@ -41505,7 +42158,7 @@ "type": "reflection", "value": { "name": "__type", - "code": "{ getWindow: (): Window, type: 'popout' }", + "code": "{ getWindow: (): Window, popoutUrl?: string, type: 'popout' }", "kind": "typeLiteral", "properties": [ { @@ -41536,6 +42189,18 @@ }, "flags": {} }, + { + "name": "popoutUrl", + "code": "string", + "kind": "property", + "type": { + "type": "intrinsic", + "value": "string" + }, + "flags": { + "isOptional": true + } + }, { "name": "type", "code": "'popout'", @@ -41721,6 +42386,27 @@ }, "kind": "typeAlias" }, + "GridviewComponentOptions": { + "name": "GridviewComponentOptions", + "code": "GridviewFrameworkOptions & GridviewOptions", + "typeParameters": [], + "type": { + "type": "intersection", + "values": [ + { + "type": "reference", + "value": "GridviewFrameworkOptions", + "source": "dockview-core" + }, + { + "type": "reference", + "value": "GridviewOptions", + "source": "dockview-core" + } + ] + }, + "kind": "typeAlias" + }, "IDockviewGroupPanelPublic": { "name": "IDockviewGroupPanelPublic", "code": "IDockviewGroupPanel", @@ -41734,18 +42420,44 @@ }, "IDockviewPanelHeaderProps": { "name": "IDockviewPanelHeaderProps", - "code": "IGroupPanelBaseProps", + "code": "{ tabLocation: TabLocation } & IGroupPanelBaseProps", "typeParameters": [], "type": { - "type": "reference", - "value": "IGroupPanelBaseProps", - "source": "dockview-core", - "typeArguments": [ + "type": "intersection", + "values": [ + { + "type": "reflection", + "value": { + "name": "__type", + "code": "{ tabLocation: TabLocation }", + "kind": "typeLiteral", + "properties": [ + { + "name": "tabLocation", + "code": "TabLocation", + "kind": "property", + "type": { + "type": "reference", + "value": "TabLocation", + "source": "dockview-core" + }, + "flags": {} + } + ] + } + }, { "type": "reference", - "value": "T", + "value": "IGroupPanelBaseProps", "source": "dockview-core", - "refersToTypeParameter": true + "typeArguments": [ + { + "type": "reference", + "value": "T", + "source": "dockview-core", + "refersToTypeParameter": true + } + ] } ] }, @@ -41872,6 +42584,27 @@ }, "kind": "typeAlias" }, + "PaneviewComponentOptions": { + "name": "PaneviewComponentOptions", + "code": "PaneviewFrameworkOptions & PaneviewOptions", + "typeParameters": [], + "type": { + "type": "intersection", + "values": [ + { + "type": "reference", + "value": "PaneviewFrameworkOptions", + "source": "dockview-core" + }, + { + "type": "reference", + "value": "PaneviewOptions", + "source": "dockview-core" + } + ] + }, + "kind": "typeAlias" + }, "Position": { "name": "Position", "code": "'center' | 'right' | 'left' | 'bottom' | 'top'", @@ -41939,8 +42672,84 @@ }, "kind": "typeAlias" }, - "PROPERTY_KEYS": { - "name": "PROPERTY_KEYS", + "SplitviewComponentOptions": { + "name": "SplitviewComponentOptions", + "code": "SplitviewFrameworkOptions & SplitviewOptions", + "typeParameters": [], + "type": { + "type": "intersection", + "values": [ + { + "type": "reference", + "value": "SplitviewFrameworkOptions", + "source": "dockview-core" + }, + { + "type": "reference", + "value": "SplitviewOptions", + "source": "dockview-core" + } + ] + }, + "kind": "typeAlias" + }, + "PROPERTY_KEYS_DOCKVIEW": { + "name": "PROPERTY_KEYS_DOCKVIEW", + "code": "", + "kind": "variable" + }, + "PROPERTY_KEYS_GRIDVIEW": { + "name": "PROPERTY_KEYS_GRIDVIEW", + "code": "", + "kind": "variable" + }, + "PROPERTY_KEYS_PANEVIEW": { + "name": "PROPERTY_KEYS_PANEVIEW", + "code": "", + "kind": "variable" + }, + "PROPERTY_KEYS_SPLITVIEW": { + "name": "PROPERTY_KEYS_SPLITVIEW", + "code": "", + "kind": "variable" + }, + "themeAbyss": { + "name": "themeAbyss", + "code": "", + "kind": "variable" + }, + "themeAbyssSpaced": { + "name": "themeAbyssSpaced", + "code": "", + "kind": "variable" + }, + "themeDark": { + "name": "themeDark", + "code": "", + "kind": "variable" + }, + "themeDracula": { + "name": "themeDracula", + "code": "", + "kind": "variable" + }, + "themeLight": { + "name": "themeLight", + "code": "", + "kind": "variable" + }, + "themeLightSpaced": { + "name": "themeLightSpaced", + "code": "", + "kind": "variable" + }, + "themeReplit": { + "name": "themeReplit", + "code": "", + "kind": "variable" + }, + "themeVisualStudio": { + "name": "themeVisualStudio", "code": "", "kind": "variable" }, @@ -42714,18 +43523,6 @@ "kind": "interface", "name": "IDockviewReactProps", "children": [ - { - "name": "className", - "code": "string", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "string" - }, - "flags": { - "isOptional": true - } - }, { "name": "components", "code": "Record>", @@ -43056,18 +43853,6 @@ "kind": "interface", "name": "IGridviewReactProps", "children": [ - { - "name": "className", - "code": "string", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "string" - }, - "flags": { - "isOptional": true - } - }, { "name": "components", "code": "Record>>", @@ -43103,30 +43888,6 @@ }, "flags": {} }, - { - "name": "disableAutoResizing", - "code": "boolean", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "boolean" - }, - "flags": { - "isOptional": true - } - }, - { - "name": "hideBorders", - "code": "boolean", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "boolean" - }, - "flags": { - "isOptional": true - } - }, { "name": "onReady", "code": "(event: GridviewReadyEvent): void", @@ -43164,44 +43925,11 @@ } }, "flags": {} - }, - { - "name": "orientation", - "code": "Orientation.VERTICAL | Orientation.HORIZONTAL", - "kind": "property", - "type": { - "type": "or", - "values": [ - { - "type": "reference", - "value": "Orientation.VERTICAL", - "source": "dockview-core" - }, - { - "type": "reference", - "value": "Orientation.HORIZONTAL", - "source": "dockview-core" - } - ] - }, - "flags": { - "isOptional": true - } - }, - { - "name": "proportionalLayout", - "code": "boolean", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "boolean" - }, - "flags": { - "isOptional": true - } } ], - "extends": [] + "extends": [ + "GridviewOptions" + ] }, "IPaneviewPanelProps": { "kind": "interface", @@ -43260,18 +43988,6 @@ "kind": "interface", "name": "IPaneviewReactProps", "children": [ - { - "name": "className", - "code": "string", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "string" - }, - "flags": { - "isOptional": true - } - }, { "name": "components", "code": "Record>>", @@ -43307,30 +44023,6 @@ }, "flags": {} }, - { - "name": "disableAutoResizing", - "code": "boolean", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "boolean" - }, - "flags": { - "isOptional": true - } - }, - { - "name": "disableDnd", - "code": "boolean", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "boolean" - }, - "flags": { - "isOptional": true - } - }, { "name": "headerComponents", "code": "Record>>", @@ -43406,49 +44098,9 @@ }, "flags": {} }, - { - "name": "showDndOverlay", - "code": "(event: PaneviewDndOverlayEvent): boolean", - "kind": "property", - "type": { - "type": "reflection", - "value": { - "name": "__type", - "code": "(event: PaneviewDndOverlayEvent): boolean", - "kind": "typeLiteral", - "signatures": [ - { - "name": "__type", - "typeParameters": [], - "parameters": [ - { - "name": "event", - "code": "event: PaneviewDndOverlayEvent", - "type": { - "type": "reference", - "value": "PaneviewDndOverlayEvent", - "source": "dockview-core" - }, - "kind": "parameter" - } - ], - "returnType": { - "type": "intrinsic", - "value": "boolean" - }, - "code": "(event: PaneviewDndOverlayEvent): boolean", - "kind": "callSignature" - } - ] - } - }, - "flags": { - "isOptional": true - } - }, { "name": "onDidDrop", - "code": "(event: PaneviewDropEvent): void", + "code": "(event: PaneviewDidDropEvent): void", "kind": "method", "signature": [ { @@ -43457,10 +44109,10 @@ "parameters": [ { "name": "event", - "code": "event: PaneviewDropEvent", + "code": "event: PaneviewDidDropEvent", "type": { "type": "reference", - "value": "PaneviewDropEvent", + "value": "PaneviewDidDropEvent", "source": "dockview-core" }, "kind": "parameter" @@ -43470,13 +44122,15 @@ "type": "intrinsic", "value": "void" }, - "code": "(event: PaneviewDropEvent): void", + "code": "(event: PaneviewDidDropEvent): void", "kind": "callSignature" } ] } ], - "extends": [] + "extends": [ + "PaneviewOptions" + ] }, "ISplitviewPanelProps": { "kind": "interface", @@ -43525,18 +44179,6 @@ "kind": "interface", "name": "ISplitviewReactProps", "children": [ - { - "name": "className", - "code": "string", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "string" - }, - "flags": { - "isOptional": true - } - }, { "name": "components", "code": "Record>>", @@ -43572,30 +44214,6 @@ }, "flags": {} }, - { - "name": "disableAutoResizing", - "code": "boolean", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "boolean" - }, - "flags": { - "isOptional": true - } - }, - { - "name": "hideBorders", - "code": "boolean", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "boolean" - }, - "flags": { - "isOptional": true - } - }, { "name": "onReady", "code": "(event: SplitviewReadyEvent): void", @@ -43633,44 +44251,11 @@ } }, "flags": {} - }, - { - "name": "orientation", - "code": "Orientation.VERTICAL | Orientation.HORIZONTAL", - "kind": "property", - "type": { - "type": "or", - "values": [ - { - "type": "reference", - "value": "Orientation.VERTICAL", - "source": "dockview-core" - }, - { - "type": "reference", - "value": "Orientation.HORIZONTAL", - "source": "dockview-core" - } - ] - }, - "flags": { - "isOptional": true - } - }, - { - "name": "proportionalLayout", - "code": "boolean", - "kind": "property", - "type": { - "type": "intrinsic", - "value": "boolean" - }, - "flags": { - "isOptional": true - } } ], - "extends": [] + "extends": [ + "SplitviewOptions" + ] }, "PanelParameters": { "kind": "interface", diff --git a/packages/docs/src/pages/demo.tsx b/packages/docs/src/pages/demo.tsx index ad48bf86c..d04d76f60 100644 --- a/packages/docs/src/pages/demo.tsx +++ b/packages/docs/src/pages/demo.tsx @@ -3,11 +3,29 @@ import Layout from '@theme/Layout'; import { themeConfig } from '../config/theme.config'; import ExampleFrame from '../components/ui/exampleFrame'; import BrowserOnly from '@docusaurus/BrowserOnly'; +import { DockviewTheme, themeAbyss } from 'dockview'; + +const updateTheme = (theme: DockviewTheme) => { + const urlParams = new URLSearchParams(window.location.search); + + urlParams.set('theme', theme.name); + + const newUrl = window.location.pathname + '?' + urlParams.toString(); + + window.history.pushState({ path: newUrl }, '', newUrl); +}; const ThemeToggle: React.FC = () => { - const [theme, setTheme] = React.useState( - new URLSearchParams(location.search).get('theme') ?? themeConfig[3].id - ); + const [theme, setTheme] = React.useState(themeAbyss); + + React.useEffect(() => { + const urlParams = new URLSearchParams(window.location.search); + const themeName = urlParams.get('theme'); + const newTheme = + themeConfig.find((c) => c.id.name === themeName)?.id ?? themeAbyss; + setTheme(newTheme); + updateTheme(newTheme); + }, []); return ( <> @@ -16,20 +34,48 @@ const ThemeToggle: React.FC = () => { height: '40px', display: 'flex', alignItems: 'center', + padding: '0px 15px', }} > - { - const url = new URL(window.location.href); - url.searchParams.set('theme', event.target.value); - window.location.href = url.toString(); + const theme = themeConfig.find( + (theme) => theme.id.name === event.target.value + ).id; + setTheme(theme); + updateTheme(theme); }} - value={theme} + value={theme.name} > {themeConfig.map((theme) => { - return ; + return ( + + ); })} - + */}
); } + +import { + DropdownMenu, + DropdownMenuItem, + DropdownMenuContent, + DropdownMenuTrigger, +} from '@radix-ui/react-dropdown-menu'; + +const ThemeSelector = (props: { + options: string[]; + value: string; + onChanged: (value: string) => void; +}) => { + const ref = React.useRef(null); + + return ( +
+ { + if (!open) { + return; + } + + if (!ref.current) { + return; + } + + requestAnimationFrame(() => { + const el = ref.current!.querySelector( + `[data-dropdown-menu-value="${props.value}"]` + ); + if (el) { + (el as HTMLElement).focus(); + } + }); + }} + > + +
+ {props.value} +
+
+ + {props.options.map((option) => { + return ( + props.onChanged(option)} + className="DropdownMenuItem" + > +
+ {option} + + {option === props.value ? '✓' : ''} + +
+
+ ); + })} +
+
+
+ ); +}; diff --git a/packages/docs/src/pages/popout.tsx b/packages/docs/src/pages/popout.tsx deleted file mode 100644 index d203a94ea..000000000 --- a/packages/docs/src/pages/popout.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import React from 'react'; - -export default function Popout() { - return
; -} diff --git a/packages/docs/src/theme/Root.tsx b/packages/docs/src/theme/Root.tsx index 516775b04..eb8c4705e 100644 --- a/packages/docs/src/theme/Root.tsx +++ b/packages/docs/src/theme/Root.tsx @@ -3,5 +3,9 @@ import { RecoilRoot } from 'recoil'; // Default implementation, that you can customize export default function Root({ children }) { - return {children}; + return ( + + {children} + + ); } diff --git a/packages/docs/static/popout.html b/packages/docs/static/popout.html new file mode 100644 index 000000000..7b3adf88d --- /dev/null +++ b/packages/docs/static/popout.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/packages/docs/templates/dockview/demo-dockview/react/src/app.tsx b/packages/docs/templates/dockview/demo-dockview/react/src/app.tsx index 3ab3066da..befc23f44 100644 --- a/packages/docs/templates/dockview/demo-dockview/react/src/app.tsx +++ b/packages/docs/templates/dockview/demo-dockview/react/src/app.tsx @@ -124,6 +124,11 @@ const DockviewDemo = (props: { theme?: string }) => { const onReady = (event: DockviewReadyEvent) => { setApi(event.api); + setPanels([]); + setGroups([]); + setActivePanel(undefined); + setActiveGroup(undefined); + addLogLine(`Dockview Is Ready`); }; React.useEffect(() => { diff --git a/packages/docs/templates/dockview/dnd-external/react/src/app.tsx b/packages/docs/templates/dockview/dnd-external/react/src/app.tsx index cb04f50a5..5aba3fcbc 100644 --- a/packages/docs/templates/dockview/dnd-external/react/src/app.tsx +++ b/packages/docs/templates/dockview/dnd-external/react/src/app.tsx @@ -113,6 +113,10 @@ const DndDockview = (props: { renderVisibleOnly: boolean; theme?: string }) => { } }); + const disposable = api.onUnhandledDragOverEvent((event) => { + event.accept(); + }); + return () => { panelDragDisposable.dispose(); groupDragDisposable.dispose(); @@ -134,10 +138,6 @@ const DndDockview = (props: { renderVisibleOnly: boolean; theme?: string }) => { }); }; - const showDndOverlay = (event: DockviewDndOverlayEvent) => { - return true; - }; - const onDrop = (event: React.DragEvent) => { const dataTransfer = event.dataTransfer; @@ -179,8 +179,7 @@ const DndDockview = (props: { renderVisibleOnly: boolean; theme?: string }) => { onReady={onReady} className={`${props.theme || 'dockview-theme-abyss'}`} onDidDrop={onDidDrop} - showDndOverlay={showDndOverlay} - rootOverlayModel={{ + dndEdges={{ size: { value: 100, type: 'pixels' }, activationSize: { value: 5, type: 'percentage' }, }} diff --git a/packages/docs/templates/dockview/group-actions/typescript/src/index.css b/packages/docs/templates/dockview/group-actions/typescript/src/index.css new file mode 100644 index 000000000..92ec05921 --- /dev/null +++ b/packages/docs/templates/dockview/group-actions/typescript/src/index.css @@ -0,0 +1,23 @@ +.dockview-groupcontrol-demo { + height: 100%; + display: flex; + align-items: center; + color: white; + background-color: black; + padding: 0px 8px; + + margin: 1px; + border: 1px dotted orange; + + +} + +.dockview-groupcontrol-demo .dockview-groupcontrol-demo-group-active { + padding: 0px 8px; +} + +.dockview-groupcontrol-demo .dockview-groupcontrol-demo-active-panel { + color: yellow; + padding: 0px 8px; +} + diff --git a/packages/docs/templates/dockview/group-actions/typescript/src/index.ts b/packages/docs/templates/dockview/group-actions/typescript/src/index.ts new file mode 100644 index 000000000..65fd84833 --- /dev/null +++ b/packages/docs/templates/dockview/group-actions/typescript/src/index.ts @@ -0,0 +1,178 @@ +import 'dockview-core/dist/styles/dockview.css'; +import { + createDockview, + DockviewGroupPanel, + GroupPanelPartInitParameters, + IContentRenderer, + IGroupHeaderProps, + IHeaderActionsRenderer, +} from 'dockview-core'; +import './index.css'; + +class Panel implements IContentRenderer { + private readonly _element: HTMLElement; + + get element(): HTMLElement { + return this._element; + } + + constructor() { + this._element = document.createElement('div'); + this._element.style.display = 'flex'; + this._element.style.justifyContent = 'center'; + this._element.style.alignItems = 'center'; + this._element.style.color = 'gray'; + this._element.style.height = '100%'; + } + + init(parameters: GroupPanelPartInitParameters): void { + // + } +} + +class PrefixHeader implements IHeaderActionsRenderer { + private readonly _element: HTMLElement; + + get element(): HTMLElement { + return this._element; + } + + constructor(group: DockviewGroupPanel) { + this._element = document.createElement('div'); + this._element.className = 'dockview-groupcontrol-demo'; + this._element.innerText = '🌲'; + } + + init(parameters: IGroupHeaderProps): void { + // + } + + dispose(): void { + // + } +} + +class RightHeaderActions implements IHeaderActionsRenderer { + private readonly _element: HTMLElement; + private readonly _disposables: (() => void)[] = []; + + get element(): HTMLElement { + return this._element; + } + + constructor(group: DockviewGroupPanel) { + this._element = document.createElement('div'); + this._element.className = 'dockview-groupcontrol-demo'; + } + + init(parameters: IGroupHeaderProps): void { + const group = parameters.group; + + const span = document.createElement('span'); + span.className = 'dockview-groupcontrol-demo-group-active'; + + this._element.appendChild(span); + + const d1 = group.api.onDidActiveChange(() => { + span.style.background = group.api.isActive ? 'green' : 'red'; + span.innerText = `${ + group.api.isActive ? 'Group Active' : 'Group Inactive' + }`; + }); + + span.style.background = group.api.isActive ? 'green' : 'red'; + span.innerText = `${ + group.api.isActive ? 'Group Active' : 'Group Inactive' + }`; + + this._disposables.push(() => d1.dispose()); + } + + dispose(): void { + this._disposables.forEach((dispose) => dispose()); + } +} + +class LeftHeaderActions implements IHeaderActionsRenderer { + private readonly _element: HTMLElement; + private readonly _disposables: (() => void)[] = []; + + get element(): HTMLElement { + return this._element; + } + + constructor(group: DockviewGroupPanel) { + console.log('group', group); + this._element = document.createElement('div'); + this._element.className = 'dockview-groupcontrol-demo'; + } + + init(parameters: IGroupHeaderProps): void { + const group = parameters.group; + + const span = document.createElement('span'); + span.className = 'dockview-groupcontrol-demo-active-panel'; + + this._element.appendChild(span); + + const d1 = group.api.onDidActivePanelChange((event) => { + console.log('event', event); + span.innerText = `activePanel: ${event.panel?.id || 'null'}`; + }); + + console.log('group.activePanel', group.activePanel); + + span.innerText = `activePanel: ${group.activePanel?.id || 'null'}`; + + this._disposables.push(() => d1.dispose()); + } + + dispose(): void { + this._disposables.forEach((dispose) => dispose()); + } +} + +const api = createDockview(document.getElementById('app'), { + className: 'dockview-theme-abyss', + createComponent: (options): IContentRenderer => { + switch (options.name) { + case 'default': + return new Panel(); + default: + throw new Error('Panel not found'); + } + }, + createPrefixHeaderActionComponent: (group): IHeaderActionsRenderer => { + return new PrefixHeader(group); + }, + createLeftHeaderActionComponent: (group): IHeaderActionsRenderer => { + return new LeftHeaderActions(group); + }, + createRightHeaderActionComponent: (group): IHeaderActionsRenderer => { + return new RightHeaderActions(group); + }, +}); + +api.addPanel({ + id: 'panel_1', + component: 'default', + title: 'Panel 1', +}); + +api.addPanel({ + id: 'panel_2', + component: 'default', + title: 'Panel 2', + position: { + direction: 'right', + }, +}); + +api.addPanel({ + id: 'panel_3', + component: 'default', + title: 'Panel 3', + position: { + direction: 'below', + }, +}); diff --git a/packages/docs/templates/dockview/nested/react/src/app.tsx b/packages/docs/templates/dockview/nested/react/src/app.tsx index 42afc7f73..5c033b453 100644 --- a/packages/docs/templates/dockview/nested/react/src/app.tsx +++ b/packages/docs/templates/dockview/nested/react/src/app.tsx @@ -70,23 +70,11 @@ const NestedDockview = (props: { theme?: string }) => { }); }; - const showDndOverlay = (event: DockviewDndOverlayEvent) => { - // console.log(event.getData()); - - return false; - }; - - const onDidDrop = (event: DockviewDidDropEvent) => { - // event.getData(); - }; - return ( ); }; diff --git a/yarn.lock b/yarn.lock index 204fe8376..1232e119a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5282,11 +5282,6 @@ address@^1.0.1, address@^1.1.2: resolved "https://registry.yarnpkg.com/address/-/address-1.2.2.tgz#2b5248dac5485a6390532c6a517fda2e3faac89e" integrity sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA== -ag-grid-community@31.1.1: - version "31.1.1" - resolved "https://registry.yarnpkg.com/ag-grid-community/-/ag-grid-community-31.1.1.tgz#212fc3e358d4be1865bc4618f6d0d865faaed385" - integrity sha512-tiQZ7VQ07yJScTMIQpaYoUMPgiyXMwYDcwTxe4riRrcYGTg0e258XEihoPUZFejR60P1fYWMxdJaR2JUnyhGrg== - ag-grid-community@^31.0.2, ag-grid-community@~31.0.2: version "31.0.2" resolved "https://registry.yarnpkg.com/ag-grid-community/-/ag-grid-community-31.0.2.tgz#8421c4e23e29205628281c1258808e83dccdada2" @@ -5300,13 +5295,6 @@ ag-grid-react@^31.0.2: ag-grid-community "~31.0.2" prop-types "^15.8.1" -ag-grid-vue3@^31.1.1: - version "31.1.1" - resolved "https://registry.yarnpkg.com/ag-grid-vue3/-/ag-grid-vue3-31.1.1.tgz#0382632cf521138c532b48c9b985c3951d69e788" - integrity sha512-GjR4/6JUWQ0VsdmcxSN5ks1vH+wB7lLjX1CbskPqNfD69/fb9l7Eg2oGAgqNJo8b87W87jzhg9RguOx5bDpsZQ== - dependencies: - ag-grid-community "31.1.1" - agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"