diff --git a/packages/dockview/src/__tests__/dnd/droptarget.spec.ts b/packages/dockview/src/__tests__/dnd/droptarget.spec.ts index 3f79aa220..724955434 100644 --- a/packages/dockview/src/__tests__/dnd/droptarget.spec.ts +++ b/packages/dockview/src/__tests__/dnd/droptarget.spec.ts @@ -3,9 +3,7 @@ import { calculateQuadrantAsPixels, directionToPosition, Droptarget, - DropTargetDirections, Position, - Quadrant, } from '../../dnd/droptarget'; import { fireEvent } from '@testing-library/dom'; @@ -36,11 +34,11 @@ describe('droptarget', () => { }); test('directionToPosition', () => { - expect(directionToPosition('above')).toBe(Position.Top); - expect(directionToPosition('below')).toBe(Position.Bottom); - expect(directionToPosition('left')).toBe(Position.Left); - expect(directionToPosition('right')).toBe(Position.Right); - expect(directionToPosition('within')).toBe(Position.Center); + expect(directionToPosition('above')).toBe('top'); + expect(directionToPosition('below')).toBe('bottom'); + expect(directionToPosition('left')).toBe('left'); + expect(directionToPosition('right')).toBe('right'); + expect(directionToPosition('within')).toBe('center'); expect(() => directionToPosition('bad_input' as any)).toThrow( "invalid direction 'bad_input'" ); @@ -65,7 +63,7 @@ describe('droptarget', () => { '.drop-target-dropzone' ) as HTMLElement; fireEvent.drop(target); - expect(position).toBe(Position.Center); + expect(position).toBe('center'); }); test('drop', () => { @@ -100,7 +98,7 @@ describe('droptarget', () => { expect(position).toBeUndefined(); fireEvent.drop(target); - expect(position).toBe(Position.Left); + expect(position).toBe('left'); }); test('default', () => { @@ -135,7 +133,7 @@ describe('droptarget', () => { '.drop-target > .drop-target-dropzone > .drop-target-selection' ); expect(viewQuery.length).toBe(1); - expect(droptarget.state).toBe(Position.Left); + expect(droptarget.state).toBe('left'); expect( ( element @@ -153,7 +151,7 @@ describe('droptarget', () => { '.drop-target > .drop-target-dropzone > .drop-target-selection' ); expect(viewQuery.length).toBe(1); - expect(droptarget.state).toBe(Position.Top); + expect(droptarget.state).toBe('top'); expect( ( element @@ -171,7 +169,7 @@ describe('droptarget', () => { '.drop-target > .drop-target-dropzone > .drop-target-selection' ); expect(viewQuery.length).toBe(1); - expect(droptarget.state).toBe(Position.Bottom); + expect(droptarget.state).toBe('bottom'); expect( ( element @@ -189,7 +187,7 @@ describe('droptarget', () => { '.drop-target > .drop-target-dropzone > .drop-target-selection' ); expect(viewQuery.length).toBe(1); - expect(droptarget.state).toBe(Position.Right); + expect(droptarget.state).toBe('right'); expect( ( element @@ -202,7 +200,7 @@ describe('droptarget', () => { target, createOffsetDragOverEvent({ clientX: 100, clientY: 50 }) ); - expect(droptarget.state).toBe(Position.Center); + expect(droptarget.state).toBe('center'); expect( ( element @@ -212,7 +210,7 @@ describe('droptarget', () => { ).toBe(''); fireEvent.dragLeave(target); - expect(droptarget.state).toBe(Position.Center); + expect(droptarget.state).toBe('center'); viewQuery = element.querySelectorAll('.drop-target'); expect(viewQuery.length).toBe(0); }); @@ -220,10 +218,10 @@ describe('droptarget', () => { describe('calculateQuadrantAsPercentage', () => { test('variety of cases', () => { const inputs: Array<{ - directions: DropTargetDirections[]; + directions: Position[]; x: number; y: number; - result: Quadrant | null | undefined; + result: Position | null; }> = [ { directions: ['left', 'right'], x: 19, y: 50, result: 'left' }, { @@ -248,13 +246,13 @@ describe('droptarget', () => { directions: ['left', 'right', 'top', 'bottom', 'center'], x: 50, y: 50, - result: null, + result: 'center', }, { directions: ['left', 'right', 'top', 'bottom'], x: 50, y: 50, - result: undefined, + result: null, }, ]; @@ -276,10 +274,10 @@ describe('droptarget', () => { describe('calculateQuadrantAsPixels', () => { test('variety of cases', () => { const inputs: Array<{ - directions: DropTargetDirections[]; + directions: Position[]; x: number; y: number; - result: Quadrant | null | undefined; + result: Position | null; }> = [ { directions: ['left', 'right'], x: 19, y: 50, result: 'left' }, { @@ -304,13 +302,13 @@ describe('droptarget', () => { directions: ['left', 'right', 'top', 'bottom', 'center'], x: 50, y: 50, - result: null, + result: 'center', }, { directions: ['left', 'right', 'top', 'bottom'], x: 50, y: 50, - result: undefined, + result: null, }, ]; diff --git a/packages/dockview/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview/src/__tests__/dockview/dockviewComponent.spec.ts index d7ed07d0d..97cb21e79 100644 --- a/packages/dockview/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview/src/__tests__/dockview/dockviewComponent.spec.ts @@ -253,9 +253,9 @@ describe('dockviewComponent', () => { const panel4 = dockview.getGroupPanel('panel4'); const group1 = panel1!.group; - dockview.moveGroupOrPanel(group1, group1.id, 'panel1', Position.Right); + dockview.moveGroupOrPanel(group1, group1.id, 'panel1', 'right'); const group2 = panel1!.group; - dockview.moveGroupOrPanel(group2, group1.id, 'panel3', Position.Center); + dockview.moveGroupOrPanel(group2, group1.id, 'panel3', 'center'); expect(dockview.activeGroup).toBe(group2); expect(dockview.activeGroup!.model.activePanel).toBe(panel3); @@ -305,9 +305,9 @@ describe('dockviewComponent', () => { const panel1 = dockview.getGroupPanel('panel1')!; const panel2 = dockview.getGroupPanel('panel2')!; const group1 = panel1.group; - dockview.moveGroupOrPanel(group1, group1.id, 'panel1', Position.Right); + dockview.moveGroupOrPanel(group1, group1.id, 'panel1', 'right'); const group2 = panel1.group; - dockview.moveGroupOrPanel(group2, group1.id, 'panel3', Position.Center); + dockview.moveGroupOrPanel(group2, group1.id, 'panel3', 'center'); expect(dockview.size).toBe(2); expect(dockview.totalPanels).toBe(4); @@ -370,9 +370,9 @@ describe('dockviewComponent', () => { expect(panel4.api.isActive).toBeFalsy(); const group1 = panel1.group; - dockview.moveGroupOrPanel(group1, group1.id, 'panel1', Position.Right); + dockview.moveGroupOrPanel(group1, group1.id, 'panel1', 'right'); const group2 = panel1.group; - dockview.moveGroupOrPanel(group2, group1.id, 'panel3', Position.Center); + dockview.moveGroupOrPanel(group2, group1.id, 'panel3', 'center'); expect(dockview.size).toBe(2); expect(panel1.group).toBe(panel3.group); @@ -439,7 +439,7 @@ describe('dockviewComponent', () => { expect(group.model.indexOf(panel1)).toBe(0); expect(group.model.indexOf(panel2)).toBe(1); - dockview.moveGroupOrPanel(group, group.id, 'panel1', Position.Right); + dockview.moveGroupOrPanel(group, group.id, 'panel1', 'right'); expect(dockview.size).toBe(2); expect(dockview.totalPanels).toBe(2); @@ -489,7 +489,7 @@ describe('dockviewComponent', () => { expect(viewQuery.length).toBe(1); const group = dockview.getGroupPanel('panel1')!.group; - dockview.moveGroupOrPanel(group, group.id, 'panel1', Position.Right); + dockview.moveGroupOrPanel(group, group.id, 'panel1', 'right'); viewQuery = container.querySelectorAll( '.branch-node > .split-view-container > .view-container > .view' @@ -974,7 +974,7 @@ describe('dockviewComponent', () => { panel2.group!, panel5.group!.id, panel5.id, - Position.Center + 'center' ); expect(events).toEqual([ { type: 'REMOVE_PANEL', panel: panel5 }, @@ -993,7 +993,7 @@ describe('dockviewComponent', () => { panel2.group!, panel4.group!.id, panel4.id, - Position.Center + 'center' ); expect(events).toEqual([ @@ -1313,7 +1313,7 @@ describe('dockviewComponent', () => { panel1.group, panel2.group.id, 'panel2', - Position.Left + 'left' ); expect(panel1Spy).not.toHaveBeenCalled(); @@ -1354,7 +1354,7 @@ describe('dockviewComponent', () => { panel1.group, panel2.group.id, 'panel2', - Position.Center + 'center' ); expect(panel1Spy).not.toHaveBeenCalled(); @@ -1393,7 +1393,7 @@ describe('dockviewComponent', () => { panel1.group, panel1.group.id, 'panel1', - Position.Center, + 'center', 0 ); @@ -1554,7 +1554,7 @@ describe('dockviewComponent', () => { panel3.group, panel1.group.id, undefined, - Position.Center + 'center' ); expect(dockview.groups.length).toBe(1); diff --git a/packages/dockview/src/__tests__/groupview/groupview.spec.ts b/packages/dockview/src/__tests__/groupview/groupview.spec.ts index b40d81295..fc9067e89 100644 --- a/packages/dockview/src/__tests__/groupview/groupview.spec.ts +++ b/packages/dockview/src/__tests__/groupview/groupview.spec.ts @@ -730,7 +730,7 @@ describe('groupview', () => { ).toBe(0); }); - test('that should allow drop when not dropping on self for same component id', () => { + test('that should not allow drop when dropping on self for same component id', () => { const accessorMock = jest.fn, []>(() => { return { id: 'testcomponentid', @@ -792,7 +792,7 @@ describe('groupview', () => { expect( element.getElementsByClassName('drop-target-dropzone').length - ).toBe(1); + ).toBe(0); }); test('that should not allow drop when not dropping for different component id', () => { diff --git a/packages/dockview/src/dnd/droptarget.ts b/packages/dockview/src/dnd/droptarget.ts index 774c7c5a5..fb5986193 100644 --- a/packages/dockview/src/dnd/droptarget.ts +++ b/packages/dockview/src/dnd/droptarget.ts @@ -9,44 +9,29 @@ function numberOrFallback(maybeNumber: any, fallback: number): number { return typeof maybeNumber === 'number' ? maybeNumber : fallback; } -export enum Position { - Top = 'Top', - Left = 'Left', - Bottom = 'Bottom', - Right = 'Right', - Center = 'Center', -} - export function directionToPosition(direction: Direction): Position { switch (direction) { case 'above': - return Position.Top; + return 'top'; case 'below': - return Position.Bottom; + return 'bottom'; case 'left': - return Position.Left; + return 'left'; case 'right': - return Position.Right; + return 'right'; case 'within': - return Position.Center; + return 'center'; default: throw new Error(`invalid direction '${direction}'`); } } -export type Quadrant = 'top' | 'bottom' | 'left' | 'right'; - export interface DroptargetEvent { readonly position: Position; readonly nativeEvent: DragEvent; } -export type DropTargetDirections = - | 'top' - | 'bottom' - | 'left' - | 'right' - | 'center'; +export type Position = 'top' | 'bottom' | 'left' | 'right' | 'center'; function isBooleanValue( canDisplayOverlay: CanDisplayOverlay @@ -56,7 +41,7 @@ function isBooleanValue( export type CanDisplayOverlay = | boolean - | ((dragEvent: DragEvent, state: Quadrant | null) => boolean); + | ((dragEvent: DragEvent, state: Position) => boolean); export class Droptarget extends CompositeDisposable { private target: HTMLElement | undefined; @@ -74,7 +59,7 @@ export class Droptarget extends CompositeDisposable { private readonly element: HTMLElement, private readonly options: { canDisplayOverlay: CanDisplayOverlay; - acceptedTargetZones: DropTargetDirections[]; + acceptedTargetZones: Position[]; overlayModel?: { size?: { value: number; type: 'pixels' | 'percentage' }; activationSize?: { @@ -117,7 +102,7 @@ export class Droptarget extends CompositeDisposable { height ); - if (quadrant === undefined) { + if (quadrant === null) { this.removeDropTarget(); return; } @@ -135,7 +120,7 @@ export class Droptarget extends CompositeDisposable { this.target.className = 'drop-target-dropzone'; this.overlay = document.createElement('div'); this.overlay.className = 'drop-target-selection'; - this._state = Position.Center; + this._state = 'center'; this.target.appendChild(this.overlay); this.element.classList.add('drop-target'); @@ -181,7 +166,7 @@ export class Droptarget extends CompositeDisposable { } private toggleClasses( - quadrant: Quadrant | null, + quadrant: Position, width: number, height: number ): void { @@ -246,33 +231,33 @@ export class Droptarget extends CompositeDisposable { toggleClass(this.overlay, 'small-bottom', isSmallY && isBottom); } - private setState(quadrant: Quadrant | null): void { + private setState(quadrant: Position): void { switch (quadrant) { case 'top': - this._state = Position.Top; + this._state = 'top'; break; case 'left': - this._state = Position.Left; + this._state = 'left'; break; case 'bottom': - this._state = Position.Bottom; + this._state = 'bottom'; break; case 'right': - this._state = Position.Right; + this._state = 'right'; break; - default: - this._state = Position.Center; + case 'center': + this._state = 'center'; break; } } private calculateQuadrant( - overlayType: Set, + overlayType: Set, x: number, y: number, width: number, height: number - ): Quadrant | null | undefined { + ): Position | null { const isPercentage = this.options.overlayModel?.activationSize === undefined || this.options.overlayModel?.activationSize?.type === 'percentage'; @@ -315,13 +300,13 @@ export class Droptarget extends CompositeDisposable { } export function calculateQuadrantAsPercentage( - overlayType: Set, + overlayType: Set, x: number, y: number, width: number, height: number, threshold: number -): Quadrant | null | undefined { +): Position | null { const xp = (100 * x) / width; const yp = (100 * y) / height; @@ -339,20 +324,20 @@ export function calculateQuadrantAsPercentage( } if (!overlayType.has('center')) { - return undefined; + return null; } - return null; + return 'center'; } export function calculateQuadrantAsPixels( - overlayType: Set, + overlayType: Set, x: number, y: number, width: number, height: number, threshold: number -): Quadrant | null | undefined { +): Position | null { if (overlayType.has('left') && x < threshold) { return 'left'; } @@ -367,8 +352,8 @@ export function calculateQuadrantAsPixels( } if (!overlayType.has('center')) { - return undefined; + return null; } - return null; + return 'center'; } diff --git a/packages/dockview/src/dockview/dockviewComponent.ts b/packages/dockview/src/dockview/dockviewComponent.ts index 6f7f2c869..648c5d77e 100644 --- a/packages/dockview/src/dockview/dockviewComponent.ts +++ b/packages/dockview/src/dockview/dockviewComponent.ts @@ -46,6 +46,7 @@ import { import { GroupPanel, IGroupviewPanel } from '../groupview/groupviewPanel'; import { DefaultGroupPanelView } from './defaultGroupPanelView'; import { getPanelData } from '../dnd/dataTransfer'; +import { DockviewDropTargets } from '../groupview/dnd'; export interface PanelReference { update: (event: { params: { [key: string]: any } }) => void; @@ -235,8 +236,26 @@ export class DockviewComponent } const dropTarget = new Droptarget(this.element, { - canDisplayOverlay: () => { - return true; + canDisplayOverlay: (event, position) => { + const data = getPanelData(); + + if (data) { + if (data.viewId !== this.id) { + return false; + } + return true; + } + + if (this.options.showDndOverlay) { + return this.options.showDndOverlay({ + nativeEvent: event, + position: position, + target: DockviewDropTargets.Edge, + getData: getPanelData, + }); + } + + return false; }, acceptedTargetZones: ['top', 'bottom', 'left', 'right'], overlayModel: { @@ -250,16 +269,14 @@ export class DockviewComponent dropTarget.onDrop((event) => { const data = getPanelData(); - if (!data) { - return; + if (data) { + this.moveGroupOrPanel( + this.orthogonalize(event.position), + data.groupId, + data.panelId || undefined, + 'center' + ); } - - this.moveGroupOrPanel( - this.orthogonalize(event.position), - data.groupId, - data.panelId || undefined, - Position.Center - ); }) ); @@ -268,16 +285,16 @@ export class DockviewComponent private orthogonalize(position: Position): GroupPanel { switch (position) { - case Position.Top: - case Position.Bottom: + case 'top': + case 'bottom': if (this.gridview.orientation === Orientation.HORIZONTAL) { // we need to add to a vertical splitview but the current root is a horizontal splitview. // insert a vertical splitview at the root level and add the existing view as a child this.gridview.insertOrthogonalSplitviewAtRoot(); } break; - case Position.Left: - case Position.Right: + case 'left': + case 'right': if (this.gridview.orientation === Orientation.VERTICAL) { // we need to add to a horizontal splitview but the current root is a vertical splitview. // insert a horiziontal splitview at the root level and add the existing view as a child @@ -289,11 +306,11 @@ export class DockviewComponent } switch (position) { - case Position.Top: - case Position.Left: + case 'top': + case 'left': return this.createGroupAtLocation([0]); // insert into first position - case Position.Bottom: - case Position.Right: + case 'bottom': + case 'right': return this.createGroupAtLocation([this.gridview.length]); // insert into last position default: throw new Error(`unsupported position ${position}`); @@ -543,7 +560,7 @@ export class DockviewComponent const target = toTarget( options.position?.direction || 'within' ); - if (target === Position.Center) { + if (target === 'center') { panel = this.createPanel(options, referenceGroup); referenceGroup.model.openPanel(panel); } else { @@ -703,7 +720,7 @@ export class DockviewComponent return; } - if (!target || target === Position.Center) { + if (!target || target === 'center') { const groupItem: IDockviewPanel | undefined = sourceGroup?.model.removePanel(itemId) || this.panels.find((panel) => panel.id === itemId); @@ -782,7 +799,7 @@ export class DockviewComponent target: Position ): void { if (sourceGroup) { - if (!target || target === Position.Center) { + if (!target || target === 'center') { const activePanel = sourceGroup.activePanel; const panels = [...sourceGroup.panels].map((p) => sourceGroup.model.removePanel(p.id) diff --git a/packages/dockview/src/dockview/options.ts b/packages/dockview/src/dockview/options.ts index 4aafa3c4c..4dc6d18ca 100644 --- a/packages/dockview/src/dockview/options.ts +++ b/packages/dockview/src/dockview/options.ts @@ -14,6 +14,7 @@ import { FrameworkFactory } from '../types'; import { DockviewDropTargets } from '../groupview/dnd'; import { PanelTransfer } from '../dnd/dataTransfer'; import { IGroupControlRenderer } from '../react/dockview/groupControlsRenderer'; +import { Position } from '../dnd/droptarget'; export interface GroupPanelFrameworkComponentFactory { content: FrameworkFactory; @@ -54,7 +55,8 @@ export interface ViewFactoryData { export interface DockviewDndOverlayEvent { nativeEvent: DragEvent; target: DockviewDropTargets; - group: GroupPanel; + position: Position; + group?: GroupPanel; getData: () => PanelTransfer | undefined; } diff --git a/packages/dockview/src/gridview/baseComponentGridview.ts b/packages/dockview/src/gridview/baseComponentGridview.ts index 58399c1e1..f45c1f108 100644 --- a/packages/dockview/src/gridview/baseComponentGridview.ts +++ b/packages/dockview/src/gridview/baseComponentGridview.ts @@ -15,19 +15,19 @@ const nextLayoutId = sequentialNumberGenerator(); export type Direction = 'left' | 'right' | 'above' | 'below' | 'within'; -export function toTarget(direction: Direction) { +export function toTarget(direction: Direction): Position { switch (direction) { case 'left': - return Position.Left; + return 'left'; case 'right': - return Position.Right; + return 'right'; case 'above': - return Position.Top; + return 'top'; case 'below': - return Position.Bottom; + return 'bottom'; case 'within': default: - return Position.Center; + return 'center'; } } diff --git a/packages/dockview/src/gridview/gridview.ts b/packages/dockview/src/gridview/gridview.ts index c47dca887..74f96b43e 100644 --- a/packages/dockview/src/gridview/gridview.ts +++ b/packages/dockview/src/gridview/gridview.ts @@ -9,13 +9,13 @@ import { Orientation, Sizing, } from '../splitview/core/splitview'; -import { Position } from '../dnd/droptarget'; import { tail } from '../array'; import { LeafNode } from './leafNode'; import { BranchNode } from './branchNode'; import { Node } from './types'; import { Emitter, Event } from '../events'; import { IDisposable, MutableDisposable } from '../lifecycle'; +import { Position } from '../dnd/droptarget'; function findLeaf(candiateNode: Node, last: boolean): LeafNode { if (candiateNode instanceof LeafNode) { @@ -132,22 +132,19 @@ export function getRelativeLocation( const [rest, _index] = tail(location); let index = _index; - if (direction === Position.Right || direction === Position.Bottom) { + if (direction === 'right' || direction === 'bottom') { index += 1; } return [...rest, index]; } else { - const index = - direction === Position.Right || direction === Position.Bottom - ? 1 - : 0; + const index = direction === 'right' || direction === 'bottom' ? 1 : 0; return [...location, index]; } } export function getDirectionOrientation(direction: Position): Orientation { - return direction === Position.Top || direction === Position.Bottom + return direction === 'top' || direction === 'bottom' ? Orientation.VERTICAL : Orientation.HORIZONTAL; } diff --git a/packages/dockview/src/gridview/gridviewComponent.ts b/packages/dockview/src/gridview/gridviewComponent.ts index daf7ce013..70d564bfe 100644 --- a/packages/dockview/src/gridview/gridviewComponent.ts +++ b/packages/dockview/src/gridview/gridviewComponent.ts @@ -3,7 +3,6 @@ import { SerializedGridObject, getGridLocation, } from './gridview'; -import { Position } from '../dnd/droptarget'; import { tail, sequenceEquals } from '../array'; import { CompositeDisposable } from '../lifecycle'; import { IPanelDeserializer } from '../dockview/deserializer'; @@ -25,6 +24,7 @@ import { BaseComponentOptions } from '../panel/types'; import { Orientation, Sizing } from '../splitview/core/splitview'; import { createComponent } from '../panel/componentFactory'; import { Emitter, Event } from '../events'; +import { Position } from '../dnd/droptarget'; export interface SerializedGridview { grid: { @@ -265,7 +265,7 @@ export class GridviewComponent } const target = toTarget(options.direction); - if (target === Position.Center) { + if (target === 'center') { throw new Error(`${target} not supported as an option`); } else { const location = getGridLocation(referenceGroup.element); @@ -294,7 +294,7 @@ export class GridviewComponent } const target = toTarget(options.position.direction); - if (target === Position.Center) { + if (target === 'center') { throw new Error(`${target} not supported as an option`); } else { const location = getGridLocation(referenceGroup.element); diff --git a/packages/dockview/src/groupview/dnd.ts b/packages/dockview/src/groupview/dnd.ts index bba4250d4..4d31a64e2 100644 --- a/packages/dockview/src/groupview/dnd.ts +++ b/packages/dockview/src/groupview/dnd.ts @@ -2,4 +2,5 @@ export enum DockviewDropTargets { Tab, Panel, TabContainer, + Edge, } diff --git a/packages/dockview/src/groupview/groupview.ts b/packages/dockview/src/groupview/groupview.ts index 3dafe9871..f36f497af 100644 --- a/packages/dockview/src/groupview/groupview.ts +++ b/packages/dockview/src/groupview/groupview.ts @@ -110,7 +110,11 @@ export interface IGroupview extends IDisposable, IGridPanelView { panel?: IDockviewPanel; suppressRoll?: boolean; }): void; - canDisplayOverlay(event: DragEvent, target: DockviewDropTargets): boolean; + canDisplayOverlay( + event: DragEvent, + position: Position, + target: DockviewDropTargets + ): boolean; } export class Groupview extends CompositeDisposable implements IGroupview { @@ -240,17 +244,23 @@ export class Groupview extends CompositeDisposable implements IGroupview { this.dropTarget = new Droptarget(this.contentContainer.element, { acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'], - canDisplayOverlay: (event, quadrant) => { - if (this.locked && !quadrant) { + canDisplayOverlay: (event, position) => { + if (this.locked && position === 'center') { return false; } const data = getPanelData(); if (data && data.viewId === this.accessor.id) { - if (data.panelId === null && data.groupId === this.id) { - // don't allow group move to drop on self - return false; + if (data.groupId === this.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 = @@ -259,7 +269,11 @@ export class Groupview extends CompositeDisposable implements IGroupview { return !groupHasOnePanelAndIsActiveDragElement; } - return this.canDisplayOverlay(event, DockviewDropTargets.Panel); + return this.canDisplayOverlay( + event, + position, + DockviewDropTargets.Panel + ); }, }); @@ -279,7 +293,7 @@ export class Groupview extends CompositeDisposable implements IGroupview { this._onDidRemovePanel, this._onDidActivePanelChange, this.tabsContainer.onDrop((event) => { - this.handleDropEvent(event.event, Position.Center, event.index); + this.handleDropEvent(event.event, 'center', event.index); }), this.contentContainer.onDidFocus(() => { this.accessor.doSetGroupActive(this.groupPanel, true); @@ -673,13 +687,18 @@ export class Groupview extends CompositeDisposable implements IGroupview { } } - canDisplayOverlay(event: DragEvent, target: DockviewDropTargets): boolean { + canDisplayOverlay( + event: DragEvent, + position: Position, + target: DockviewDropTargets + ): boolean { // custom overlay handler if (this.accessor.options.showDndOverlay) { return this.accessor.options.showDndOverlay({ nativeEvent: event, target, group: this.accessor.getPanel(this.id)!, + position, getData: getPanelData, }); } diff --git a/packages/dockview/src/groupview/tab.ts b/packages/dockview/src/groupview/tab.ts index 499aa9cdd..72b1e44e7 100644 --- a/packages/dockview/src/groupview/tab.ts +++ b/packages/dockview/src/groupview/tab.ts @@ -98,7 +98,7 @@ export class Tab extends CompositeDisposable implements ITab { this.droptarget = new Droptarget(this._element, { acceptedTargetZones: ['center'], - canDisplayOverlay: (event) => { + canDisplayOverlay: (event, position) => { if (this.group.locked) { return false; } @@ -119,6 +119,7 @@ export class Tab extends CompositeDisposable implements ITab { return this.group.model.canDisplayOverlay( event, + position, DockviewDropTargets.Tab ); }, diff --git a/packages/dockview/src/groupview/titlebar/voidContainer.ts b/packages/dockview/src/groupview/titlebar/voidContainer.ts index 302029333..70990ca7c 100644 --- a/packages/dockview/src/groupview/titlebar/voidContainer.ts +++ b/packages/dockview/src/groupview/titlebar/voidContainer.ts @@ -42,7 +42,7 @@ export class VoidContainer extends CompositeDisposable { this.voidDropTarget = new Droptarget(this._element, { acceptedTargetZones: ['center'], - canDisplayOverlay: (event) => { + canDisplayOverlay: (event, position) => { const data = getPanelData(); if (data && this.accessor.id === data.viewId) { @@ -60,6 +60,7 @@ export class VoidContainer extends CompositeDisposable { return group.model.canDisplayOverlay( event, + position, DockviewDropTargets.Panel ); }, diff --git a/packages/dockview/src/index.ts b/packages/dockview/src/index.ts index 8177eb720..3d3ed5688 100644 --- a/packages/dockview/src/index.ts +++ b/packages/dockview/src/index.ts @@ -27,7 +27,7 @@ export * from './react'; // TODO: should be conditional on whether user wants th export { Event } from './events'; export { IDisposable } from './lifecycle'; -export { Position } from './dnd/droptarget'; +export { Position as DropTargetDirections } from './dnd/droptarget'; export { FocusEvent, PanelDimensionChangeEvent, diff --git a/packages/dockview/src/paneview/draggablePaneviewPanel.ts b/packages/dockview/src/paneview/draggablePaneviewPanel.ts index 77fcdc304..55e3b26a5 100644 --- a/packages/dockview/src/paneview/draggablePaneviewPanel.ts +++ b/packages/dockview/src/paneview/draggablePaneviewPanel.ts @@ -4,7 +4,7 @@ import { LocalSelectionTransfer, PaneTransfer, } from '../dnd/dataTransfer'; -import { Droptarget, DroptargetEvent, Position } from '../dnd/droptarget'; +import { Droptarget, DroptargetEvent } from '../dnd/droptarget'; import { Emitter } from '../events'; import { IDisposable } from '../lifecycle'; import { Orientation } from '../splitview/core/splitview'; @@ -142,16 +142,10 @@ export abstract class DraggablePaneviewPanel extends PaneviewPanel { const fromIndex = allPanels.indexOf(existingPanel); let toIndex = containerApi.panels.indexOf(this); - if ( - event.position === Position.Left || - event.position === Position.Top - ) { + if (event.position === 'left' || event.position === 'top') { toIndex = Math.max(0, toIndex - 1); } - if ( - event.position === Position.Right || - event.position === Position.Bottom - ) { + if (event.position === 'right' || event.position === 'bottom') { if (fromIndex > toIndex) { toIndex++; }