diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index 4827137de..4f22e6133 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -934,6 +934,9 @@ describe('dockviewComponent', () => { }), dockview.onDidActivePanelChange((panel) => { events.push({ type: 'ACTIVE_PANEL', panel }); + }), + dockview.onDidMovePanel(({ panel }) => { + events.push({ type: 'MOVE_PANEL', panel }); }) ); @@ -1016,7 +1019,10 @@ describe('dockviewComponent', () => { to: { group: panel2.group, position: 'center' }, }); - expect(events).toEqual([{ type: 'ACTIVE_GROUP', group: panel2.group }]); + expect(events).toEqual([ + { type: 'ACTIVE_GROUP', group: panel2.group }, + { type: 'MOVE_PANEL', panel: panel5 }, + ]); events = []; @@ -1030,6 +1036,7 @@ describe('dockviewComponent', () => { expect(events).toEqual([ { type: 'REMOVE_GROUP', group: groupReferenceBeforeMove }, { type: 'ACTIVE_PANEL', panel: panel4 }, + { type: 'MOVE_PANEL', panel: panel4 }, ]); for (const panel of dockview.panels) { @@ -1771,6 +1778,7 @@ describe('dockviewComponent', () => { let addPanel: IDockviewPanel[] = []; let removePanel: IDockviewPanel[] = []; let activePanel: (IDockviewPanel | undefined)[] = []; + let movedPanels: IDockviewPanel[] = []; let layoutChange = 0; let layoutChangeFromJson = 0; @@ -1793,6 +1801,9 @@ describe('dockviewComponent', () => { dockview.onDidActivePanelChange((event) => { activePanel.push(event); }), + dockview.onDidMovePanel((event) => { + movedPanels.push(event.panel); + }), dockview.onDidLayoutChange(() => { layoutChange++; }), @@ -1884,6 +1895,7 @@ describe('dockviewComponent', () => { expect(addPanel.length).toBe(5); expect(removePanel.length).toBe(0); expect(activePanel.length).toBe(1); + expect(movedPanels.length).toBe(0); expect(layoutChange).toBe(1); expect(layoutChangeFromJson).toBe(1); @@ -1918,6 +1930,7 @@ describe('dockviewComponent', () => { expect(addPanel.length).toBe(0); expect(removePanel.length).toBe(5); expect(activePanel.length).toBe(1); + expect(movedPanels.length).toBe(0); expect(layoutChange).toBe(1); expect(layoutChangeFromJson).toBe(1); @@ -4924,7 +4937,6 @@ describe('dockviewComponent', () => { describe('that emits onDidLayoutChange', () => { let dockview: DockviewComponent; - let panel1: DockviewPanel; beforeEach(() => { jest.useFakeTimers(); @@ -4943,11 +4955,6 @@ describe('dockviewComponent', () => { } }, }); - - panel1 = dockview.addPanel({ - id: 'panel_1', - component: 'default', - }); }); afterEach(() => { @@ -4955,7 +4962,63 @@ describe('dockviewComponent', () => { jest.useRealTimers(); }); + test('when panels or groups change', () => { + const didLayoutChangeHandler = jest.fn(); + dockview.onDidLayoutChange(didLayoutChangeHandler); + + // add panel + const panel1 = dockview.addPanel({ + id: 'panel_1', + component: 'default', + }); + const panel2 = dockview.addPanel({ + id: 'panel_2', + component: 'default', + position: { referenceGroup: panel1.group }, + }); + jest.runAllTimers(); + expect(didLayoutChangeHandler).toHaveBeenCalledTimes(1); + + // add group + const group = dockview.addGroup(); + jest.runAllTimers(); + expect(didLayoutChangeHandler).toHaveBeenCalledTimes(2); + + // remove group + group.api.close(); + jest.runAllTimers(); + expect(didLayoutChangeHandler).toHaveBeenCalledTimes(3); + + // active panel + panel1.api.setActive(); + jest.runAllTimers(); + expect(didLayoutChangeHandler).toHaveBeenCalledTimes(4); + + // move panel + dockview.moveGroupOrPanel({ + from: { + groupId: panel1.group.api.id, + panelId: panel1.api.id, + }, + to: { + group: panel1.group, + position: 'center', + index: 1, + }, + }); + + // remove panel + panel2.api.close(); + jest.runAllTimers(); + expect(didLayoutChangeHandler).toHaveBeenCalledTimes(5); + }); + test('that emits onDidPanelTitleChange and onDidLayoutChange when the panel set a title', () => { + const panel1 = dockview.addPanel({ + id: 'panel_1', + component: 'default', + }); + const didLayoutChangeHandler = jest.fn(); const { dispose: disposeDidLayoutChangeHandler } = dockview.onDidLayoutChange(didLayoutChangeHandler); @@ -4970,6 +5033,11 @@ describe('dockviewComponent', () => { }); test('that emits onDidPanelParametersChange and onDidLayoutChange when the panel updates parameters', () => { + const panel1 = dockview.addPanel({ + id: 'panel_1', + component: 'default', + }); + const didLayoutChangeHandler = jest.fn(); const { dispose: disposeDidLayoutChangeHandler } = dockview.onDidLayoutChange(didLayoutChangeHandler); diff --git a/packages/dockview-core/src/api/component.api.ts b/packages/dockview-core/src/api/component.api.ts index 1da34f822..2bbb41fad 100644 --- a/packages/dockview-core/src/api/component.api.ts +++ b/packages/dockview-core/src/api/component.api.ts @@ -1,5 +1,6 @@ import { IDockviewComponent, + MovePanelEvent, SerializedDockview, } from '../dockview/dockviewComponent'; import { @@ -636,6 +637,10 @@ export class DockviewApi implements CommonApi { return this.component.onDidRemovePanel; } + get onDidMovePanel(): Event { + return this.component.onDidMovePanel; + } + /** * Invoked after a layout is deserialzied using the `fromJSON` method. */ diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 8342faa9b..68fd51850 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -148,6 +148,11 @@ export interface SerializedDockview { popoutGroups?: SerializedPopoutGroup[]; } +export interface MovePanelEvent { + panel: IDockviewPanel; + from: DockviewGroupPanel; +} + type MoveGroupOptions = { from: { group: DockviewGroupPanel }; to: { group: DockviewGroupPanel; position: Position }; @@ -183,6 +188,7 @@ export interface IDockviewComponent extends IBaseGrid { readonly onDidAddGroup: Event; readonly onDidActiveGroupChange: Event; readonly onUnhandledDragOverEvent: Event; + readonly onDidMovePanel: Event; readonly options: DockviewComponentOptions; updateOptions(options: DockviewOptions): void; moveGroupOrPanel(options: MoveGroupOrPanelOptions): void; @@ -272,9 +278,8 @@ export class DockviewComponent readonly onDidActivePanelChange: Event = this._onDidActivePanelChange.event; - private readonly _onDidMovePanel = new Emitter<{ - panel: IDockviewPanel; - }>(); + private readonly _onDidMovePanel = new Emitter(); + readonly onDidMovePanel = this._onDidMovePanel.event; private readonly _floatingGroups: DockviewFloatingGroupPanel[] = []; private readonly _popoutGroups: { @@ -285,8 +290,6 @@ export class DockviewComponent }[] = []; private readonly _rootDropTarget: Droptarget; - private _ignoreEvents = 0; - private readonly _onDidRemoveGroup = new Emitter(); readonly onDidRemoveGroup: Event = this._onDidRemoveGroup.event; @@ -393,9 +396,12 @@ export class DockviewComponent )(() => { this.updateWatermark(); }), - Event.any( + Event.any( this.onDidAddPanel, this.onDidRemovePanel, + this.onDidAddGroup, + this.onDidRemove, + this.onDidMovePanel, this.onDidActivePanelChange )(() => { this._bufferOnDidLayoutChange.fire(); @@ -1797,6 +1803,7 @@ export class DockviewComponent this._onDidMovePanel.fire({ panel: removedPanel, + from: sourceGroup, }); } else { /** @@ -1833,6 +1840,12 @@ export class DockviewComponent // if a group has one tab - we are essentially moving the 'group' // which is equivalent to swapping two views in this case this.gridview.moveView(sourceParentLocation, from, to); + + this._onDidMovePanel.fire({ + panel: this.getGroupPanel(sourceItemId)!, + from: sourceGroup, + }); + return; } } @@ -1857,6 +1870,11 @@ export class DockviewComponent ); this.movingLock(() => this.doAddGroup(targetGroup, location)); this.doSetGroupAndPanelActive(targetGroup); + + this._onDidMovePanel.fire({ + panel: this.getGroupPanel(sourceItemId)!, + from: sourceGroup, + }); } else { /** * The group we are removing from has many panels, we need to remove the panels we are moving, @@ -1887,6 +1905,11 @@ export class DockviewComponent }) ); this.doSetGroupAndPanelActive(group); + + this._onDidMovePanel.fire({ + panel: removedPanel, + from: sourceGroup, + }); } } } @@ -1921,10 +1944,6 @@ export class DockviewComponent }); this.doSetGroupAndPanelActive(to); - - panels.forEach((panel) => { - this._onDidMovePanel.fire({ panel }); - }); } else { switch (from.api.location.type) { case 'grid': @@ -1959,11 +1978,11 @@ export class DockviewComponent ); this.gridview.addView(from, Sizing.Distribute, dropLocation); - - from.panels.forEach((panel) => { - this._onDidMovePanel.fire({ panel }); - }); } + + from.panels.forEach((panel) => { + this._onDidMovePanel.fire({ panel, from }); + }); } override doSetGroupActive(group: DockviewGroupPanel | undefined): void { 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 7631749e1..d1cfa65b6 100644 --- a/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx +++ b/packages/docs/sandboxes/react/dockview/demo-dockview/src/app.tsx @@ -26,6 +26,7 @@ const components = { overflow: 'auto', color: 'white', position: 'relative', + // border: '5px dashed purple', }} > {/* */} @@ -146,6 +147,10 @@ const DockviewDemo = (props: { theme?: string }) => { addLogLine(`Group Added ${event.id}`); }); + event.api.onDidMovePanel((event) => { + addLogLine(`Panel Moved ${event.panel.id}`); + }); + event.api.onDidRemoveGroup((event) => { setGroups((_) => { const next = [..._];