diff --git a/packages/dockview-core/src/__tests__/dnd/abstractDragHandler.spec.ts b/packages/dockview-core/src/__tests__/dnd/abstractDragHandler.spec.ts index 95ce9f4a4..7ce70bae1 100644 --- a/packages/dockview-core/src/__tests__/dnd/abstractDragHandler.spec.ts +++ b/packages/dockview-core/src/__tests__/dnd/abstractDragHandler.spec.ts @@ -176,4 +176,120 @@ describe('abstractDragHandler', () => { handler.dispose(); }); + + test('that disabled handler calls preventDefault on dragstart', () => { + const element = document.createElement('div'); + + const handler = new (class TestClass extends DragHandler { + constructor(el: HTMLElement, disabled?: boolean) { + super(el, disabled); + } + + getData(): IDisposable { + return { + dispose: () => { + // / + }, + }; + } + })(element, true); + + const event = new Event('dragstart'); + const spy = jest.spyOn(event, 'preventDefault'); + fireEvent(element, event); + expect(spy).toBeCalledTimes(1); + + handler.dispose(); + }); + + test('that non-disabled handler does not call preventDefault on dragstart', () => { + const element = document.createElement('div'); + + const handler = new (class TestClass extends DragHandler { + constructor(el: HTMLElement, disabled?: boolean) { + super(el, disabled); + } + + getData(): IDisposable { + return { + dispose: () => { + // / + }, + }; + } + })(element, false); + + const event = new Event('dragstart'); + const spy = jest.spyOn(event, 'preventDefault'); + fireEvent(element, event); + expect(spy).toHaveBeenCalledTimes(0); + + handler.dispose(); + }); + + test('that setDisabled method updates disabled state', () => { + const element = document.createElement('div'); + + const handler = new (class TestClass extends DragHandler { + constructor(el: HTMLElement, disabled?: boolean) { + super(el, disabled); + } + + getData(): IDisposable { + return { + dispose: () => { + // / + }, + }; + } + })(element, false); + + // Initially not disabled + let event = new Event('dragstart'); + let spy = jest.spyOn(event, 'preventDefault'); + fireEvent(element, event); + expect(spy).toHaveBeenCalledTimes(0); + + // Disable and test + handler.setDisabled(true); + event = new Event('dragstart'); + spy = jest.spyOn(event, 'preventDefault'); + fireEvent(element, event); + expect(spy).toBeCalledTimes(1); + + // Re-enable and test + handler.setDisabled(false); + event = new Event('dragstart'); + spy = jest.spyOn(event, 'preventDefault'); + fireEvent(element, event); + expect(spy).toHaveBeenCalledTimes(0); + + handler.dispose(); + }); + + test('that disabled handler does not fire onDragStart event', () => { + const element = document.createElement('div'); + + const handler = new (class TestClass extends DragHandler { + constructor(el: HTMLElement, disabled?: boolean) { + super(el, disabled); + } + + getData(): IDisposable { + return { + dispose: () => { + // / + }, + }; + } + })(element, true); + + const spy = jest.fn(); + handler.onDragStart(spy); + + fireEvent.dragStart(element); + expect(spy).toHaveBeenCalledTimes(0); + + handler.dispose(); + }); }); 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 4652a46b9..917b390ee 100644 --- a/packages/dockview-core/src/__tests__/dockview/components/tab.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/components/tab.spec.ts @@ -338,5 +338,104 @@ describe('tab', () => { cut.updateDragAndDropState(); expect(cut.element.draggable).toBe(true); }); + + test('that dragstart is prevented when disableDnd is true', () => { + const accessor = fromPartial({ + options: { disableDnd: true } + }); + const groupMock = jest.fn(); + + const cut = new Tab( + { id: 'panelId' } as IDockviewPanel, + accessor, + new groupMock() + ); + + const event = new Event('dragstart'); + const spy = jest.spyOn(event, 'preventDefault'); + fireEvent(cut.element, event); + expect(spy).toHaveBeenCalledTimes(1); + + cut.dispose(); + }); + + test('that dragstart is not prevented when disableDnd is false', () => { + const accessor = fromPartial({ + options: { disableDnd: false } + }); + const groupMock = jest.fn(); + + const cut = new Tab( + { id: 'panelId' } as IDockviewPanel, + accessor, + new groupMock() + ); + + const event = new Event('dragstart'); + const spy = jest.spyOn(event, 'preventDefault'); + fireEvent(cut.element, event); + expect(spy).toHaveBeenCalledTimes(0); + + cut.dispose(); + }); + + test('that updateDragAndDropState updates drag handler disabled state', () => { + const options = { disableDnd: false }; + const accessor = fromPartial({ + options + }); + const groupMock = jest.fn(); + + const cut = new Tab( + { id: 'panelId' } as IDockviewPanel, + accessor, + new groupMock() + ); + + // Initially not disabled + let event = new Event('dragstart'); + let spy = jest.spyOn(event, 'preventDefault'); + fireEvent(cut.element, event); + expect(spy).toHaveBeenCalledTimes(0); + + // Simulate option change to disabled + options.disableDnd = true; + cut.updateDragAndDropState(); + event = new Event('dragstart'); + spy = jest.spyOn(event, 'preventDefault'); + fireEvent(cut.element, event); + expect(spy).toHaveBeenCalledTimes(1); + + // Change back to enabled + options.disableDnd = false; + cut.updateDragAndDropState(); + event = new Event('dragstart'); + spy = jest.spyOn(event, 'preventDefault'); + fireEvent(cut.element, event); + expect(spy).toHaveBeenCalledTimes(0); + + cut.dispose(); + }); + + test('that onDragStart is not fired when disableDnd is true', () => { + const accessor = fromPartial({ + options: { disableDnd: true } + }); + const groupMock = jest.fn(); + + const cut = new Tab( + { id: 'panelId' } as IDockviewPanel, + accessor, + new groupMock() + ); + + const spy = jest.fn(); + cut.onDragStart(spy); + + fireEvent.dragStart(cut.element); + expect(spy).toHaveBeenCalledTimes(0); + + cut.dispose(); + }); }); }); diff --git a/packages/dockview-core/src/__tests__/dockview/components/titlebar/voidContainer.spec.ts b/packages/dockview-core/src/__tests__/dockview/components/titlebar/voidContainer.spec.ts index 78c04596f..256cae045 100644 --- a/packages/dockview-core/src/__tests__/dockview/components/titlebar/voidContainer.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/components/titlebar/voidContainer.spec.ts @@ -111,5 +111,100 @@ describe('voidContainer', () => { cut.updateDragAndDropState(); expect(cut.element.classList.contains('dv-draggable')).toBe(true); }); + + test('that dragstart is prevented when disableDnd is true', () => { + const accessor = fromPartial({ + options: { disableDnd: true } + }); + const group = fromPartial({ + api: { + location: { type: 'grid' } + } + }); + const cut = new VoidContainer(accessor, group); + + const event = new Event('dragstart'); + const spy = jest.spyOn(event, 'preventDefault'); + fireEvent(cut.element, event); + expect(spy).toHaveBeenCalledTimes(1); + + cut.dispose(); + }); + + test('that dragstart is not prevented when disableDnd is false', () => { + const accessor = fromPartial({ + options: { disableDnd: false } + }); + const group = fromPartial({ + api: { + location: { type: 'grid' } + } + }); + const cut = new VoidContainer(accessor, group); + + const event = new Event('dragstart'); + const spy = jest.spyOn(event, 'preventDefault'); + fireEvent(cut.element, event); + expect(spy).toHaveBeenCalledTimes(0); + + cut.dispose(); + }); + + test('that updateDragAndDropState updates drag handler disabled state', () => { + const options = { disableDnd: false }; + const accessor = fromPartial({ + options + }); + const group = fromPartial({ + api: { + location: { type: 'grid' } + } + }); + const cut = new VoidContainer(accessor, group); + + // Initially not disabled + let event = new Event('dragstart'); + let spy = jest.spyOn(event, 'preventDefault'); + fireEvent(cut.element, event); + expect(spy).toHaveBeenCalledTimes(0); + + // Simulate option change to disabled + options.disableDnd = true; + cut.updateDragAndDropState(); + event = new Event('dragstart'); + spy = jest.spyOn(event, 'preventDefault'); + fireEvent(cut.element, event); + expect(spy).toHaveBeenCalledTimes(1); + + // Change back to enabled + options.disableDnd = false; + cut.updateDragAndDropState(); + event = new Event('dragstart'); + spy = jest.spyOn(event, 'preventDefault'); + fireEvent(cut.element, event); + expect(spy).toHaveBeenCalledTimes(0); + + cut.dispose(); + }); + + test('that onDragStart is not fired when disableDnd is true', () => { + const accessor = fromPartial({ + options: { disableDnd: true } + }); + const group = fromPartial({ + api: { + location: { type: 'grid' } + } + }); + const cut = new VoidContainer(accessor, group); + + const spy = jest.fn(); + cut.onDragStart(spy); + + fireEvent.dragStart(cut.element); + expect(spy).toHaveBeenCalledTimes(0); + + cut.dispose(); + }); }); }); diff --git a/packages/dockview-core/src/dnd/abstractDragHandler.ts b/packages/dockview-core/src/dnd/abstractDragHandler.ts index 7ba701034..fef13073f 100644 --- a/packages/dockview-core/src/dnd/abstractDragHandler.ts +++ b/packages/dockview-core/src/dnd/abstractDragHandler.ts @@ -13,7 +13,7 @@ export abstract class DragHandler extends CompositeDisposable { private readonly _onDragStart = new Emitter(); readonly onDragStart = this._onDragStart.event; - constructor(protected readonly el: HTMLElement) { + constructor(protected readonly el: HTMLElement, private disabled?: boolean) { super(); this.addDisposables( @@ -25,6 +25,10 @@ export abstract class DragHandler extends CompositeDisposable { this.configure(); } + public setDisabled(disabled: boolean): void { + this.disabled = disabled; + } + abstract getData(event: DragEvent): IDisposable; protected isCancelled(_event: DragEvent): boolean { @@ -35,7 +39,7 @@ export abstract class DragHandler extends CompositeDisposable { this.addDisposables( this._onDragStart, addDisposableListener(this.el, 'dragstart', (event) => { - if (event.defaultPrevented || this.isCancelled(event)) { + if (event.defaultPrevented || this.isCancelled(event) || this.disabled) { event.preventDefault(); return; } diff --git a/packages/dockview-core/src/dnd/groupDragHandler.ts b/packages/dockview-core/src/dnd/groupDragHandler.ts index 2e3c9d281..7291e54f2 100644 --- a/packages/dockview-core/src/dnd/groupDragHandler.ts +++ b/packages/dockview-core/src/dnd/groupDragHandler.ts @@ -14,9 +14,10 @@ export class GroupDragHandler extends DragHandler { constructor( element: HTMLElement, private readonly accessor: DockviewComponent, - private readonly group: DockviewGroupPanel + private readonly group: DockviewGroupPanel, + disabled?: boolean ) { - super(element); + super(element, disabled); this.addDisposables( addDisposableListener( diff --git a/packages/dockview-core/src/dockview/components/tab/tab.ts b/packages/dockview-core/src/dockview/components/tab/tab.ts index af1943780..e6b2e6be6 100644 --- a/packages/dockview-core/src/dockview/components/tab/tab.ts +++ b/packages/dockview-core/src/dockview/components/tab/tab.ts @@ -26,9 +26,10 @@ class TabDragHandler extends DragHandler { element: HTMLElement, private readonly accessor: DockviewComponent, private readonly group: DockviewGroupPanel, - private readonly panel: IDockviewPanel + private readonly panel: IDockviewPanel, + disabled?: boolean ) { - super(element); + super(element, disabled); } getData(event: DragEvent): IDisposable { @@ -49,6 +50,7 @@ export class Tab extends CompositeDisposable { private readonly _element: HTMLElement; private readonly dropTarget: Droptarget; private content: ITabRenderer | undefined = undefined; + private readonly dragHandler: TabDragHandler; private readonly _onPointDown = new Emitter(); readonly onPointerDown: Event = this._onPointDown.event; @@ -79,11 +81,12 @@ export class Tab extends CompositeDisposable { toggleClass(this.element, 'dv-inactive-tab', true); - const dragHandler = new TabDragHandler( + this.dragHandler = new TabDragHandler( this._element, this.accessor, this.group, - this.panel + this.panel, + !!this.accessor.options.disableDnd ); this.dropTarget = new Droptarget(this._element, { @@ -115,7 +118,7 @@ export class Tab extends CompositeDisposable { this._onPointDown, this._onDropped, this._onDragStart, - dragHandler.onDragStart((event) => { + this.dragHandler.onDragStart((event) => { if (event.dataTransfer) { const style = getComputedStyle(this.element); const newNode = this.element.cloneNode(true) as HTMLElement; @@ -135,7 +138,7 @@ export class Tab extends CompositeDisposable { } this._onDragStart.fire(event); }), - dragHandler, + this.dragHandler, addDisposableListener(this._element, 'pointerdown', (event) => { this._onPointDown.fire(event); }), @@ -161,6 +164,7 @@ export class Tab extends CompositeDisposable { public updateDragAndDropState(): void { this._element.draggable = !this.accessor.options.disableDnd; + this.dragHandler.setDisabled(!!this.accessor.options.disableDnd); } public dispose(): void { diff --git a/packages/dockview-core/src/dockview/components/titlebar/voidContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/voidContainer.ts index 49ca94cb0..94b9f7d0c 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/voidContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/voidContainer.ts @@ -15,6 +15,7 @@ import { toggleClass } from '../../../dom'; export class VoidContainer extends CompositeDisposable { private readonly _element: HTMLElement; private readonly dropTarget: Droptarget; + private readonly handler: GroupDragHandler; private readonly _onDrop = new Emitter(); readonly onDrop: Event = this._onDrop.event; @@ -49,7 +50,7 @@ export class VoidContainer extends CompositeDisposable { }) ); - const handler = new GroupDragHandler(this._element, accessor, group); + this.handler = new GroupDragHandler(this._element, accessor, group, !!this.accessor.options.disableDnd); this.dropTarget = new Droptarget(this._element, { acceptedTargetZones: ['center'], @@ -72,8 +73,8 @@ export class VoidContainer extends CompositeDisposable { this.onWillShowOverlay = this.dropTarget.onWillShowOverlay; this.addDisposables( - handler, - handler.onDragStart((event) => { + this.handler, + this.handler.onDragStart((event) => { this._onDragStart.fire(event); }), this.dropTarget.onDrop((event) => { @@ -86,5 +87,6 @@ export class VoidContainer extends CompositeDisposable { updateDragAndDropState(): void { this._element.draggable = !this.accessor.options.disableDnd; toggleClass(this._element, 'dv-draggable', !this.accessor.options.disableDnd); + this.handler.setDisabled(!!this.accessor.options.disableDnd); } }