diff --git a/packages/dockview-core/src/__tests__/dnd/groupDragHandler.spec.ts b/packages/dockview-core/src/__tests__/dnd/groupDragHandler.spec.ts index b597f2fa5..7ca528766 100644 --- a/packages/dockview-core/src/__tests__/dnd/groupDragHandler.spec.ts +++ b/packages/dockview-core/src/__tests__/dnd/groupDragHandler.spec.ts @@ -2,6 +2,7 @@ import { fireEvent } from '@testing-library/dom'; import { GroupDragHandler } from '../../dnd/groupDragHandler'; import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel'; import { LocalSelectionTransfer, PanelTransfer } from '../../dnd/dataTransfer'; +import { DockviewComponent } from '../../dockview/dockviewComponent'; describe('groupDragHandler', () => { test('that the dnd transfer object is setup and torndown', () => { @@ -16,7 +17,11 @@ describe('groupDragHandler', () => { }); const group = new groupMock(); - const cut = new GroupDragHandler(element, 'test_accessor_id', group); + const cut = new GroupDragHandler( + element, + { id: 'test_accessor_id' } as DockviewComponent, + group + ); fireEvent.dragStart(element, new Event('dragstart')); @@ -54,7 +59,11 @@ describe('groupDragHandler', () => { }); const group = new groupMock(); - const cut = new GroupDragHandler(element, 'accessor_id', group); + const cut = new GroupDragHandler( + element, + { id: 'accessor_id' } as DockviewComponent, + group + ); const event = new KeyboardEvent('dragstart', { shiftKey: false }); @@ -82,7 +91,11 @@ describe('groupDragHandler', () => { }); const group = new groupMock(); - const cut = new GroupDragHandler(element, 'accessor_id', group); + const cut = new GroupDragHandler( + element, + { id: 'accessor_id' } as DockviewComponent, + group + ); const event = new KeyboardEvent('dragstart', { shiftKey: false }); 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 2aa671d00..bd45df9f1 100644 --- a/packages/dockview-core/src/__tests__/dockview/components/tab.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/components/tab.spec.ts @@ -1,16 +1,24 @@ import { fireEvent } from '@testing-library/dom'; -import { LocalSelectionTransfer, PanelTransfer } from '../../../dnd/dataTransfer'; +import { + LocalSelectionTransfer, + PanelTransfer, +} from '../../../dnd/dataTransfer'; import { DockviewComponent } from '../../../dockview/dockviewComponent'; import { DockviewGroupPanel } from '../../../dockview/dockviewGroupPanel'; import { DockviewGroupPanelModel } from '../../../dockview/dockviewGroupPanelModel'; import { Tab } from '../../../dockview/components/tab/tab'; +import { IDockviewPanel } from '../../../dockview/dockviewPanel'; describe('tab', () => { test('that empty tab has inactive-tab class', () => { const accessorMock = jest.fn(); const groupMock = jest.fn(); - const cut = new Tab('panelId', new accessorMock(), new groupMock()); + const cut = new Tab( + { id: 'panelId' } as IDockviewPanel, + new accessorMock(), + new groupMock() + ); expect(cut.element.className).toBe('tab inactive-tab'); }); @@ -19,7 +27,11 @@ describe('tab', () => { const accessorMock = jest.fn(); const groupMock = jest.fn(); - const cut = new Tab('panelId', new accessorMock(), new groupMock()); + const cut = new Tab( + { id: 'panelId' } as IDockviewPanel, + new accessorMock(), + new groupMock() + ); cut.setActive(true); expect(cut.element.className).toBe('tab active-tab'); @@ -54,7 +66,11 @@ describe('tab', () => { const accessor = new accessorMock() as DockviewComponent; const groupPanel = new groupPanelMock() as DockviewGroupPanel; - const cut = new Tab('panelId', accessor, groupPanel); + const cut = new Tab( + { id: 'panelId' } as IDockviewPanel, + accessor, + groupPanel + ); jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation( () => 100 @@ -99,7 +115,11 @@ describe('tab', () => { const accessor = new accessorMock() as DockviewComponent; const groupPanel = new groupPanelMock() as DockviewGroupPanel; - const cut = new Tab('panel1', accessor, groupPanel); + const cut = new Tab( + { id: 'panel1' } as IDockviewPanel, + accessor, + groupPanel + ); jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation( () => 100 @@ -149,7 +169,11 @@ describe('tab', () => { const accessor = new accessorMock() as DockviewComponent; const groupPanel = new groupPanelMock() as DockviewGroupPanel; - const cut = new Tab('panel1', accessor, groupPanel); + const cut = new Tab( + { id: 'panel1' } as IDockviewPanel, + accessor, + groupPanel + ); jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation( () => 100 @@ -199,7 +223,11 @@ describe('tab', () => { const accessor = new accessorMock() as DockviewComponent; const groupPanel = new groupPanelMock() as DockviewGroupPanel; - const cut = new Tab('panel1', accessor, groupPanel); + const cut = new Tab( + { id: 'panel1' } as IDockviewPanel, + accessor, + groupPanel + ); jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation( () => 100 @@ -255,7 +283,11 @@ describe('tab', () => { const accessor = new accessorMock() as DockviewComponent; const groupPanel = new groupPanelMock() as DockviewGroupPanel; - const cut = new Tab('panel1', accessor, groupPanel); + const cut = new Tab( + { id: 'panel1' } as IDockviewPanel, + accessor, + groupPanel + ); jest.spyOn(cut.element, 'clientHeight', 'get').mockImplementation( () => 100 diff --git a/packages/dockview-core/src/dnd/abstractDragHandler.ts b/packages/dockview-core/src/dnd/abstractDragHandler.ts index cb02cc18e..22426e2a6 100644 --- a/packages/dockview-core/src/dnd/abstractDragHandler.ts +++ b/packages/dockview-core/src/dnd/abstractDragHandler.ts @@ -35,8 +35,6 @@ export abstract class DragHandler extends CompositeDisposable { this.addDisposables( this._onDragStart, addDisposableListener(this.el, 'dragstart', (event) => { - this._onDragStart.fire(event); - if (event.defaultPrevented || this.isCancelled(event)) { event.preventDefault(); return; @@ -63,6 +61,7 @@ export abstract class DragHandler extends CompositeDisposable { setTimeout(() => this.el.classList.remove('dv-dragged'), 0); this.dataDisposable.value = this.getData(event); + this._onDragStart.fire(event); if (event.dataTransfer) { event.dataTransfer.effectAllowed = 'move'; diff --git a/packages/dockview-core/src/dnd/groupDragHandler.ts b/packages/dockview-core/src/dnd/groupDragHandler.ts index 9cc5d58a9..fd6d42f77 100644 --- a/packages/dockview-core/src/dnd/groupDragHandler.ts +++ b/packages/dockview-core/src/dnd/groupDragHandler.ts @@ -1,3 +1,4 @@ +import { DockviewComponent } from '../dockview/dockviewComponent'; import { DockviewGroupPanel } from '../dockview/dockviewGroupPanel'; import { quasiPreventDefault } from '../dom'; import { addDisposableListener } from '../events'; @@ -12,7 +13,7 @@ export class GroupDragHandler extends DragHandler { constructor( element: HTMLElement, - private readonly accessorId: string, + private readonly accessor: DockviewComponent, private readonly group: DockviewGroupPanel ) { super(element); @@ -47,7 +48,7 @@ export class GroupDragHandler extends DragHandler { const dataTransfer = dragEvent.dataTransfer; this.panelTransfer.setData( - [new PanelTransfer(this.accessorId, this.group.id, null)], + [new PanelTransfer(this.accessor.id, this.group.id, null)], PanelTransfer.prototype ); diff --git a/packages/dockview-core/src/dockview/components/tab/tab.ts b/packages/dockview-core/src/dockview/components/tab/tab.ts index ae396ea85..48da83a85 100644 --- a/packages/dockview-core/src/dockview/components/tab/tab.ts +++ b/packages/dockview-core/src/dockview/components/tab/tab.ts @@ -26,7 +26,7 @@ class TabDragHandler extends DragHandler { super(element); } - getData(): IDisposable { + getData(event: DragEvent): IDisposable { this.panelTransfer.setData( [new PanelTransfer(this.accessor.id, this.group.id, this.panel.id)], PanelTransfer.prototype diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts index 2ff387d23..4a3931cf2 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts @@ -18,7 +18,12 @@ export interface TabDropIndexEvent { export interface TabDragEvent { readonly nativeEvent: DragEvent; - readonly panel?: IDockviewPanel; + readonly panel: IDockviewPanel; +} + +export interface GroupDragEvent { + readonly nativeEvent: DragEvent; + readonly group: DockviewGroupPanel; } export interface ITabsContainer extends IDisposable { @@ -29,7 +34,8 @@ export interface ITabsContainer extends IDisposable { delete: (id: string) => void; indexOf: (id: string) => number; onDrop: Event; - onDragStart: Event; + onTabDragStart: Event; + onGroupDragStart: Event; setActive: (isGroupActive: boolean) => void; setActivePanel: (panel: IDockviewPanel) => void; isActive: (tab: ITab) => boolean; @@ -61,8 +67,12 @@ export class TabsContainer private readonly _onDrop = new Emitter(); readonly onDrop: Event = this._onDrop.event; - private readonly _onDragStart = new Emitter(); - readonly onDragStart: Event = this._onDragStart.event; + private readonly _onTabDragStart = new Emitter(); + readonly onTabDragStart: Event = this._onTabDragStart.event; + + private readonly _onGroupDragStart = new Emitter(); + readonly onGroupDragStart: Event = + this._onGroupDragStart.event; get panels(): string[] { return this.tabs.map((_) => _.value.panel.id); @@ -140,7 +150,11 @@ export class TabsContainer ) { super(); - this.addDisposables(this._onDrop, this._onDragStart); + this.addDisposables( + this._onDrop, + this._onTabDragStart, + this._onGroupDragStart + ); this._element = document.createElement('div'); this._element.className = 'tabs-and-actions-container'; @@ -190,6 +204,12 @@ export class TabsContainer this.addDisposables( this.voidContainer, + this.voidContainer.onDragStart((event) => { + this._onGroupDragStart.fire({ + nativeEvent: event, + group: this.group, + }); + }), this.voidContainer.onDrop((event) => { this._onDrop.fire({ event: event.nativeEvent, @@ -302,7 +322,7 @@ export class TabsContainer const disposable = new CompositeDisposable( tab.onDragStart((event) => { - this._onDragStart.fire({ nativeEvent: event, panel }); + this._onTabDragStart.fire({ nativeEvent: event, panel }); }), tab.onChanged((event) => { const isFloatingGroupsEnabled = diff --git a/packages/dockview-core/src/dockview/components/titlebar/voidContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/voidContainer.ts index 1737c87aa..e1d3cd9c7 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/voidContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/voidContainer.ts @@ -15,6 +15,9 @@ export class VoidContainer extends CompositeDisposable { private readonly _onDrop = new Emitter(); readonly onDrop: Event = this._onDrop.event; + private readonly _onDragStart = new Emitter(); + readonly onDragStart = this._onDragStart.event; + get element(): HTMLElement { return this._element; } @@ -33,12 +36,13 @@ export class VoidContainer extends CompositeDisposable { this.addDisposables( this._onDrop, + this._onDragStart, addDisposableListener(this._element, 'click', () => { this.accessor.doSetGroupActive(this.group); }) ); - const handler = new GroupDragHandler(this._element, accessor.id, group); + const handler = new GroupDragHandler(this._element, accessor, group); this.voidDropTarget = new Droptarget(this._element, { acceptedTargetZones: ['center'], @@ -68,6 +72,9 @@ export class VoidContainer extends CompositeDisposable { this.addDisposables( handler, + handler.onDragStart((event) => { + this._onDragStart.fire(event); + }), this.voidDropTarget.onDrop((event) => { this._onDrop.fire(event); }), diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 72f714906..9821fe1c4 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -50,6 +50,10 @@ import { DockviewFloatingGroupPanel, IDockviewFloatingGroupPanel, } from './dockviewFloatingGroupPanel'; +import { + GroupDragEvent, + TabDragEvent, +} from './components/titlebar/tabsContainer'; export interface PanelReference { update: (event: { params: { [key: string]: any } }) => void; @@ -130,6 +134,8 @@ export interface IDockviewComponent extends IBaseGrid { readonly onDidAddPanel: Event; readonly onDidLayoutFromJSON: Event; readonly onDidActivePanelChange: Event; + readonly onWillDragPanel: Event; + readonly onWillDragGroup: Event; addFloatingGroup( item: IDockviewPanel | DockviewGroupPanel, coord?: { x: number; y: number } @@ -146,6 +152,13 @@ export class DockviewComponent private _options: Exclude; private watermark: IWatermarkRenderer | null = null; + readonly _onWillDragPanel = new Emitter(); + readonly onWillDragPanel: Event = this._onWillDragPanel.event; + + readonly _onWillDragGroup = new Emitter(); + readonly onWillDragGroup: Event = + this._onWillDragGroup.event; + private readonly _onDidDrop = new Emitter(); readonly onDidDrop: Event = this._onDidDrop.event; @@ -204,6 +217,12 @@ export class DockviewComponent toggleClass(this.gridview.element, 'dv-dockview', true); this.addDisposables( + this._onWillDragPanel, + this._onWillDragGroup, + this._onDidActivePanelChange, + this._onDidAddPanel, + this._onDidRemovePanel, + this._onDidLayoutFromJSON, this._onDidDrop, Event.any( this.onDidAddGroup, @@ -1157,8 +1176,11 @@ export class DockviewComponent if (!this._groups.has(view.id)) { const disposable = new CompositeDisposable( - view.model.onDragStart((event) => { - this.onDragStart(event); + view.model.onTabDragStart((event) => { + this._onWillDragPanel.fire(event); + }), + view.model.onGroupDragStart((event) => { + this._onWillDragGroup.fire(event); }), view.model.onMove((event) => { const { groupId, itemId, target, index } = event; @@ -1235,13 +1257,4 @@ export class DockviewComponent group.value.model.containsPanel(panel) )?.value; } - - public dispose(): void { - this._onDidActivePanelChange.dispose(); - this._onDidAddPanel.dispose(); - this._onDidRemovePanel.dispose(); - this._onDidLayoutFromJSON.dispose(); - - super.dispose(); - } } diff --git a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts index 95850e221..407b3cf36 100644 --- a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts +++ b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts @@ -12,6 +12,7 @@ import { IContentContainer, } from './components/panel/content'; import { + GroupDragEvent, ITabsContainer, TabDragEvent, TabsContainer, @@ -92,6 +93,8 @@ export interface IDockviewGroupPanelModel extends IPanel { readonly onDidRemovePanel: Event; readonly onDidActivePanelChange: Event; readonly onMove: Event; + readonly onTabDragStart: Event; + readonly onGroupDragStart: Event; locked: boolean; setActive(isActive: boolean): void; initialize(): void; @@ -159,8 +162,12 @@ export class DockviewGroupPanelModel private readonly _onDidDrop = new Emitter(); readonly onDidDrop: Event = this._onDidDrop.event; - private readonly _onDragStart = new Emitter(); - readonly onDragStart: Event = this._onDragStart.event; + private readonly _onTabDragStart = new Emitter(); + readonly onTabDragStart: Event = this._onTabDragStart.event; + + private readonly _onGroupDragStart = new Emitter(); + readonly onGroupDragStart: Event = + this._onGroupDragStart.event; private readonly _onDidAddPanel = new Emitter(); readonly onDidAddPanel: Event = @@ -310,8 +317,13 @@ export class DockviewGroupPanelModel this.locked = !!options.locked; this.addDisposables( - this.tabsContainer.onDragStart((event) => { - this._onDragStart.fire(event); + this._onTabDragStart, + this._onGroupDragStart, + this.tabsContainer.onTabDragStart((event) => { + this._onTabDragStart.fire(event); + }), + this.tabsContainer.onGroupDragStart((event) => { + this._onGroupDragStart.fire(event); }), this.tabsContainer.onDrop((event) => { this.handleDropEvent(event.event, 'center', event.index);