diff --git a/packages/dockview/src/__tests__/dnd/abstractDragHandler.spec.ts b/packages/dockview/src/__tests__/dnd/abstractDragHandler.spec.ts new file mode 100644 index 000000000..6e1b68600 --- /dev/null +++ b/packages/dockview/src/__tests__/dnd/abstractDragHandler.spec.ts @@ -0,0 +1,87 @@ +import { fireEvent } from '@testing-library/dom'; +import { DragHandler } from '../../dnd/abstractDragHandler'; +import { IDisposable } from '../../lifecycle'; + +describe('abstractDragHandler', () => { + test('that className dragged is added to element after dragstart event', () => { + jest.useFakeTimers(); + + const element = document.createElement('div'); + + const handler = new (class TestClass extends DragHandler { + constructor(el: HTMLElement) { + super(el); + } + + getData(): IDisposable { + return { + dispose: () => { + // / + }, + }; + } + + dispose(): void { + super.dispose(); + } + })(element); + + expect(element.classList.contains('dragged')).toBeFalsy(); + + fireEvent.dragStart(element); + expect(element.classList.contains('dragged')).toBeTruthy(); + + jest.runAllTimers(); + expect(element.classList.contains('dragged')).toBeFalsy(); + + handler.dispose(); + }); + + test('that iframes and webviews have pointerEvents=none set whilst drag action is in process', () => { + jest.useFakeTimers(); + + const element = document.createElement('div'); + const iframe = document.createElement('iframe'); + const webview = document.createElement('webview'); + const span = document.createElement('span'); + + document.body.appendChild(element); + document.body.appendChild(iframe); + document.body.appendChild(webview); + document.body.appendChild(span); + + const handler = new (class TestClass extends DragHandler { + constructor(el: HTMLElement) { + super(el); + } + + getData(): IDisposable { + return { + dispose: () => { + // / + }, + }; + } + + dispose(): void { + // + } + })(element); + + expect(iframe.style.pointerEvents).toBeFalsy(); + expect(webview.style.pointerEvents).toBeFalsy(); + expect(span.style.pointerEvents).toBeFalsy(); + + fireEvent.dragStart(element); + expect(iframe.style.pointerEvents).toBe('none'); + expect(webview.style.pointerEvents).toBe('none'); + expect(span.style.pointerEvents).toBeFalsy(); + + fireEvent.dragEnd(element); + expect(iframe.style.pointerEvents).toBe('auto'); + expect(webview.style.pointerEvents).toBe('auto'); + expect(span.style.pointerEvents).toBeFalsy(); + + handler.dispose(); + }); +}); diff --git a/packages/dockview/src/__tests__/dnd/dataTransfer.spec.ts b/packages/dockview/src/__tests__/dnd/dataTransfer.spec.ts new file mode 100644 index 000000000..d188747d2 --- /dev/null +++ b/packages/dockview/src/__tests__/dnd/dataTransfer.spec.ts @@ -0,0 +1,101 @@ +import { + getPaneData, + getPanelData, + LocalSelectionTransfer, + PanelTransfer, + PaneTransfer, +} from '../../dnd/dataTransfer'; + +describe('dataTransfer', () => { + describe('getPanelData', () => { + test('should be undefined when there is no local transfer object', () => { + expect(getPanelData()).toBeUndefined(); + }); + + test('should be undefined when there is a local transfer object that is not a PanelTransfer', () => { + LocalSelectionTransfer.getInstance().setData( + [new PaneTransfer('viewId', 'groupId')], + PaneTransfer.prototype + ); + + expect(getPanelData()).toBeUndefined(); + }); + + test('should retrieve the PanelTransfer object when transfer is active', () => { + const transferObject = new PanelTransfer( + 'viewId', + 'groupId', + 'panelId' + ); + LocalSelectionTransfer.getInstance().setData( + [transferObject], + PanelTransfer.prototype + ); + + expect(getPanelData()).toBe(transferObject); + }); + + test('should retrieve the PanelTransfer when a new transfer overrides an existing one', () => { + LocalSelectionTransfer.getInstance().setData( + [new PaneTransfer('viewId', 'groupId')], + PaneTransfer.prototype + ); + + expect(getPanelData()).toBeUndefined(); + + const transferObject = new PanelTransfer( + 'viewId', + 'groupId', + 'panelId' + ); + LocalSelectionTransfer.getInstance().setData( + [transferObject], + PanelTransfer.prototype + ); + + expect(getPanelData()).toBe(transferObject); + }); + }); + + describe('getPaneData', () => { + test('should be undefined when there is no local transfer object', () => { + expect(getPaneData()).toBeUndefined(); + }); + + test('should be undefined when there is a local transfer object that is not a PaneTransfer', () => { + LocalSelectionTransfer.getInstance().setData( + [new PanelTransfer('viewId', 'groupId', 'panelId')], + PanelTransfer.prototype + ); + + expect(getPaneData()).toBeUndefined(); + }); + + test('should retrieve the PaneTransfer object when transfer is active', () => { + const transferObject = new PaneTransfer('viewId', 'groupId'); + LocalSelectionTransfer.getInstance().setData( + [transferObject], + PaneTransfer.prototype + ); + + expect(getPaneData()).toBe(transferObject); + }); + + test('should retrieve the PanelTransfer when a new transfer overrides an existing one', () => { + LocalSelectionTransfer.getInstance().setData( + [new PanelTransfer('viewId', 'groupId', 'panelId')], + PanelTransfer.prototype + ); + + expect(getPaneData()).toBeUndefined(); + + const transferObject = new PaneTransfer('viewId', 'groupId'); + LocalSelectionTransfer.getInstance().setData( + [transferObject], + PaneTransfer.prototype + ); + + expect(getPaneData()).toBe(transferObject); + }); + }); +}); diff --git a/packages/dockview/src/dnd/dataTransfer.ts b/packages/dockview/src/dnd/dataTransfer.ts index 5ccefe006..687e219a4 100644 --- a/packages/dockview/src/dnd/dataTransfer.ts +++ b/packages/dockview/src/dnd/dataTransfer.ts @@ -1,10 +1,7 @@ -import { PanelOptions } from '../dockview/options'; -import { tryParseJSON } from '../json'; - class TransferObject { - constructor() { - // - } + constructor() { + // + } } export class PanelTransfer extends TransferObject { @@ -26,65 +23,6 @@ export class PaneTransfer extends TransferObject { } } - -export const DATA_KEY = 'splitview/transfer'; - -export const isPanelTransferEvent = (event: DragEvent) => { - if (!event.dataTransfer) { - return false; - } - - return event.dataTransfer.types.includes(DATA_KEY); -}; - -export enum DragType { - DOCKVIEW_TAB = 'dockview_tab', - EXTERNAL = 'external_group_drag', -} - -export interface DragItem { - itemId: string; - groupId: string; -} - -export interface ExternalDragItem extends PanelOptions {} - -export type DataObject = DragItem | ExternalDragItem; - -/** - * Determine whether this data belong to that of an event that was started by - * dragging a tab component - */ -export const isTabDragEvent = (data: any): data is DragItem => { - return data.type === DragType.DOCKVIEW_TAB; -}; - -/** - * Determine whether this data belong to that of an event that was started by - * a custom drag-enable component - */ -export const isCustomDragEvent = (data: any): data is ExternalDragItem => { - return data.type === DragType.EXTERNAL; -}; - -export const extractData = (event: DragEvent): DataObject | null => { - if (!event.dataTransfer) { - return null; - } - - const data = tryParseJSON(event.dataTransfer.getData(DATA_KEY)); - - if (!data) { - console.warn(`[dragEvent] ${DATA_KEY} data is missing`); - } - - if (typeof data.type !== 'string') { - console.warn(`[dragEvent] invalid type ${data.type}`); - } - - return data; -}; - /** * A singleton to store transfer data during drag & drop operations that are only valid within the application. */ diff --git a/packages/dockview/src/dnd/dnd.ts b/packages/dockview/src/dnd/dnd.ts index b5e0ee9bf..4d2a38fdc 100644 --- a/packages/dockview/src/dnd/dnd.ts +++ b/packages/dockview/src/dnd/dnd.ts @@ -1,6 +1,5 @@ -import { addDisposableListener, Emitter } from '../events'; -import { CompositeDisposable, IDisposable } from '../lifecycle'; -import { LocalSelectionTransfer } from './dataTransfer'; +import { addDisposableListener } from '../events'; +import { CompositeDisposable } from '../lifecycle'; export interface IDragAndDropObserverCallbacks { onDragEnter: (e: DragEvent) => void; @@ -85,97 +84,3 @@ export interface ICompositeDragAndDropObserverCallbacks { onDragStart?: (e: IDraggedCompositeData) => void; onDragEnd?: (e: IDraggedCompositeData) => void; } - -class DockviewIdentifier { - constructor(private readonly data: T) { - // - } -} - -export class DragAndDrop extends CompositeDisposable { - private _onDragStart = new Emitter(); - private _onDragEnd = new Emitter(); - private static _instance: DragAndDrop | undefined; - static get INSTANCE(): DragAndDrop { - if (!DragAndDrop._instance) { - DragAndDrop._instance = new DragAndDrop(); - } - return DragAndDrop._instance; - } - - private transferData = - LocalSelectionTransfer.getInstance(); - - private constructor() { - super(); - - this.addDisposables(this._onDragStart, this._onDragEnd); - } - - registerTarget( - element: HTMLElement, - callbacks: ICompositeDragAndDropObserverCallbacks - ): IDisposable { - const disposables = new CompositeDisposable(); - - disposables.addDisposables( - new DragAndDropObserver(element, { - onDragEnd: (e) => { - // no-op - }, - onDragEnter: (e) => { - e.preventDefault(); - }, - onDragLeave: (e) => { - // - }, - onDrop: (e) => { - // - }, - onDragOver: (e) => { - // - }, - }) - ); - - return disposables; - } - - registerDraggable( - element: HTMLElement, - draggedItemProvider: () => { type: string; id: string }, - callbacks: ICompositeDragAndDropObserverCallbacks - ): IDisposable { - element.draggable = true; - - const disposables = new CompositeDisposable(); - - disposables.addDisposables( - addDisposableListener(element, 'dragstart', (e) => { - this._onDragStart.fire({ event: e }); - }) - ); - - disposables.addDisposables( - new DragAndDropObserver(element, { - onDragEnd: (e) => { - // no-op - }, - onDragEnter: (e) => { - // - }, - onDragLeave: (e) => { - // - }, - onDrop: (e) => { - // - }, - onDragOver: (e) => { - // - }, - }) - ); - - return disposables; - } -}