From 512a8d2c72d347191c618891ecb9c62892560999 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Wed, 17 Jan 2024 22:34:10 +0000 Subject: [PATCH 1/5] feat: dnd control changes --- .../dockview/dockviewComponent.spec.ts | 11 ++--- .../src/dockview/components/panel/content.ts | 7 +-- .../src/dockview/components/tab/tab.ts | 4 +- .../components/titlebar/voidContainer.ts | 7 +-- .../src/dockview/dockviewComponent.ts | 8 +--- .../src/dockview/dockviewGroupPanelModel.ts | 47 ++++++++++--------- packages/dockview-core/src/dockview/types.ts | 7 +-- 7 files changed, 37 insertions(+), 54 deletions(-) diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index 9dd25066d..8fdcd3d2d 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -1,6 +1,5 @@ import { DockviewComponent } from '../../dockview/dockviewComponent'; import { - DockviewDropTargets, GroupPanelPartInitParameters, IContentRenderer, ITabRenderer, @@ -2967,7 +2966,7 @@ describe('dockviewComponent', () => { expect(showDndOverlay).toHaveBeenCalledWith({ nativeEvent: eventLeft, position: 'left', - target: DockviewDropTargets.Edge, + target: 'edge', getData: getPanelData, }); expect(showDndOverlay).toBeCalledTimes(1); @@ -2986,7 +2985,7 @@ describe('dockviewComponent', () => { expect(showDndOverlay).toHaveBeenCalledWith({ nativeEvent: eventRight, position: 'right', - target: DockviewDropTargets.Edge, + target: 'edge', getData: getPanelData, }); expect(showDndOverlay).toBeCalledTimes(2); @@ -3005,7 +3004,7 @@ describe('dockviewComponent', () => { expect(showDndOverlay).toHaveBeenCalledWith({ nativeEvent: eventTop, position: 'top', - target: DockviewDropTargets.Edge, + target: 'edge', getData: getPanelData, }); expect(showDndOverlay).toBeCalledTimes(3); @@ -3024,7 +3023,7 @@ describe('dockviewComponent', () => { expect(showDndOverlay).toHaveBeenCalledWith({ nativeEvent: eventBottom, position: 'bottom', - target: DockviewDropTargets.Edge, + target: 'edge', getData: getPanelData, }); expect(showDndOverlay).toBeCalledTimes(4); @@ -3060,7 +3059,7 @@ describe('dockviewComponent', () => { expect(showDndOverlay).toHaveBeenCalledWith({ nativeEvent: eventTop, position: 'center', - target: DockviewDropTargets.Edge, + target: 'edge', getData: getPanelData, }); expect(showDndOverlay).toBeCalledTimes(5); diff --git a/packages/dockview-core/src/dockview/components/panel/content.ts b/packages/dockview-core/src/dockview/components/panel/content.ts index 91ea210cf..3b1a9534d 100644 --- a/packages/dockview-core/src/dockview/components/panel/content.ts +++ b/packages/dockview-core/src/dockview/components/panel/content.ts @@ -10,7 +10,6 @@ import { DockviewComponent } from '../../dockviewComponent'; import { Droptarget } from '../../../dnd/droptarget'; import { DockviewGroupPanelModel } from '../../dockviewGroupPanelModel'; import { getPanelData } from '../../../dnd/dataTransfer'; -import { DockviewDropTargets } from '../../types'; export interface IContentContainer extends IDisposable { readonly dropTarget: Droptarget; @@ -95,11 +94,7 @@ export class ContentContainer return !groupHasOnePanelAndIsActiveDragElement; } - return this.group.canDisplayOverlay( - event, - position, - DockviewDropTargets.Panel - ); + return this.group.canDisplayOverlay(event, position, 'panel'); }, }); diff --git a/packages/dockview-core/src/dockview/components/tab/tab.ts b/packages/dockview-core/src/dockview/components/tab/tab.ts index 48da83a85..b26c754c7 100644 --- a/packages/dockview-core/src/dockview/components/tab/tab.ts +++ b/packages/dockview-core/src/dockview/components/tab/tab.ts @@ -7,7 +7,7 @@ import { } from '../../../dnd/dataTransfer'; import { toggleClass } from '../../../dom'; import { DockviewComponent } from '../../dockviewComponent'; -import { DockviewDropTargets, ITabRenderer } from '../../types'; +import { ITabRenderer } from '../../types'; import { DockviewGroupPanel } from '../../dockviewGroupPanel'; import { DroptargetEvent, Droptarget } from '../../../dnd/droptarget'; import { DragHandler } from '../../../dnd/abstractDragHandler'; @@ -112,7 +112,7 @@ export class Tab extends CompositeDisposable implements ITab { return this.group.model.canDisplayOverlay( event, position, - DockviewDropTargets.Tab + 'tab' ); }, }); diff --git a/packages/dockview-core/src/dockview/components/titlebar/voidContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/voidContainer.ts index e1d3cd9c7..659a1d3a8 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/voidContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/voidContainer.ts @@ -6,7 +6,6 @@ import { DockviewComponent } from '../../dockviewComponent'; import { addDisposableListener, Emitter, Event } from '../../../events'; import { CompositeDisposable } from '../../../lifecycle'; import { DockviewGroupPanel } from '../../dockviewGroupPanel'; -import { DockviewDropTargets } from '../../types'; export class VoidContainer extends CompositeDisposable { private readonly _element: HTMLElement; @@ -62,11 +61,7 @@ export class VoidContainer extends CompositeDisposable { return last(this.group.panels)?.id !== data.panelId; } - return group.model.canDisplayOverlay( - event, - position, - DockviewDropTargets.Panel - ); + return group.model.canDisplayOverlay(event, position, 'panel'); }, }); diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index fd3139105..f36aeaf9d 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -10,11 +10,7 @@ import { DockviewPanel, IDockviewPanel } from './dockviewPanel'; import { CompositeDisposable, Disposable } from '../lifecycle'; import { Event, Emitter } from '../events'; import { Watermark } from './components/watermark/watermark'; -import { - IWatermarkRenderer, - GroupviewPanelState, - DockviewDropTargets, -} from './types'; +import { IWatermarkRenderer, GroupviewPanelState } from './types'; import { sequentialNumberGenerator } from '../math'; import { DefaultDockviewDeserialzier } from './deserializer'; import { createComponent } from '../panel/componentFactory'; @@ -455,7 +451,7 @@ export class DockviewComponent return this.options.showDndOverlay({ nativeEvent: event, position: position, - target: DockviewDropTargets.Edge, + target: 'edge', getData: getPanelData, }); } diff --git a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts index 9d8c9b18c..6837fe618 100644 --- a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts +++ b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts @@ -22,26 +22,6 @@ import { DockviewGroupPanel } from './dockviewGroupPanel'; import { IDockviewPanel } from './dockviewPanel'; import { IHeaderActionsRenderer } from './options'; -export interface DndService { - canDisplayOverlay( - group: IDockviewGroupPanelModel, - event: DragEvent, - target: DockviewDropTargets - ): boolean; - onDrop( - group: IDockviewGroupPanelModel, - event: DragEvent, - position: Position, - index?: number - ): void; -} - -export interface IGroupItem { - id: string; - header: { element: HTMLElement }; - body: { element: HTMLElement }; -} - interface GroupMoveEvent { groupId: string; itemId?: string; @@ -321,7 +301,12 @@ export class DockviewGroupPanelModel this._onGroupDragStart.fire(event); }), this.tabsContainer.onDrop((event) => { - this.handleDropEvent(event.event, 'center', event.index); + this.handleDropEvent( + 'header', + event.event, + 'center', + event.index + ); }), this.contentContainer.onDidFocus(() => { this.accessor.doSetGroupActive(this.groupPanel, true); @@ -330,7 +315,11 @@ export class DockviewGroupPanelModel // noop }), this.contentContainer.dropTarget.onDrop((event) => { - this.handleDropEvent(event.nativeEvent, event.position); + this.handleDropEvent( + 'content', + event.nativeEvent, + event.position + ); }), this._onMove, this._onDidChange, @@ -775,6 +764,7 @@ export class DockviewGroupPanelModel } private handleDropEvent( + type: 'header' | 'content', event: DragEvent, position: Position, index?: number @@ -783,6 +773,17 @@ export class DockviewGroupPanelModel return; } + function getKind(): 'tab' | 'header_space' | 'content' { + switch (type) { + case 'header': + return typeof index === 'number' ? 'tab' : 'header_space'; + case 'content': + return 'content'; + } + } + + const kind = getKind(); + const data = getPanelData(); if (data && data.viewId === this.accessor.id) { @@ -790,6 +791,7 @@ export class DockviewGroupPanelModel // this is a group move dnd event const { groupId } = data; + // TODO: intercept this._onMove.fire({ target: position, groupId: groupId, @@ -814,6 +816,7 @@ export class DockviewGroupPanelModel } } + // TODO: intercept this._onMove.fire({ target: position, groupId: data.groupId, diff --git a/packages/dockview-core/src/dockview/types.ts b/packages/dockview-core/src/dockview/types.ts index 28cbcc557..0059a3952 100644 --- a/packages/dockview-core/src/dockview/types.ts +++ b/packages/dockview-core/src/dockview/types.ts @@ -7,12 +7,7 @@ import { Optional } from '../types'; import { DockviewGroupPanel, IDockviewGroupPanel } from './dockviewGroupPanel'; import { DockviewPanelRenderer } from '../overlayRenderContainer'; -export enum DockviewDropTargets { - Tab, - Panel, - TabContainer, - Edge, -} +export type DockviewDropTargets = 'tab' | 'panel' | 'tabContainer' | 'edge'; export interface HeaderPartInitParameters { title: string; From 6a1f47d4daf6bb372037328c82e200180804cad5 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Thu, 18 Jan 2024 21:57:11 +0000 Subject: [PATCH 2/5] feat: expose onWillDrop --- .../dockview-core/src/api/component.api.ts | 25 ++- packages/dockview-core/src/dnd/droptarget.ts | 68 +++++-- .../src/dockview/components/tab/tab.ts | 30 +-- .../components/titlebar/tabsContainer.ts | 102 +++++----- .../components/titlebar/voidContainer.ts | 18 +- .../src/dockview/dockviewComponent.ts | 77 ++++++-- .../src/dockview/dockviewGroupPanelModel.ts | 181 ++++++++++++++---- .../dockview-core/src/dockview/options.ts | 1 + packages/dockview-core/src/events.ts | 12 ++ packages/dockview/src/dockview/dockview.tsx | 35 +++- 10 files changed, 413 insertions(+), 136 deletions(-) diff --git a/packages/dockview-core/src/api/component.api.ts b/packages/dockview-core/src/api/component.api.ts index d4a8aae02..7f5097f37 100644 --- a/packages/dockview-core/src/api/component.api.ts +++ b/packages/dockview-core/src/api/component.api.ts @@ -1,5 +1,4 @@ import { - DockviewDropEvent, IDockviewComponent, SerializedDockview, } from '../dockview/dockviewComponent'; @@ -43,6 +42,11 @@ import { TabDragEvent, } from '../dockview/components/titlebar/tabsContainer'; import { Box } from '../types'; +import { + DockviewDidDropEvent, + DockviewWillDropEvent, + WillShowOverlayLocationEvent, +} from '../dockview/dockviewGroupPanelModel'; export interface CommonApi { readonly height: number; @@ -648,10 +652,27 @@ export class DockviewApi implements CommonApi { /** * Invoked when a Drag'n'Drop event occurs that the component was unable to handle. Exposed for custom Drag'n'Drop functionality. */ - get onDidDrop(): Event { + get onDidDrop(): Event { return this.component.onDidDrop; } + /** + * Invoked when a Drag'n'Drop event occurs but before dockview handles it giving the user an opportunity to intecept and + * prevent the event from occuring using the standard `preventDefault()` syntax. + * + * Preventing certain events may causes unexpected behaviours, use carefully. + */ + get onWillDrop(): Event { + return this.component.onWillDrop; + } + + /** + * + */ + get onWillShowOverlay(): Event { + return this.component.onWillShowOverlay; + } + /** * Invoked before a group is dragged. Exposed for custom Drag'n'Drop functionality. */ diff --git a/packages/dockview-core/src/dnd/droptarget.ts b/packages/dockview-core/src/dnd/droptarget.ts index f607f2cc7..e7c22cfdc 100644 --- a/packages/dockview-core/src/dnd/droptarget.ts +++ b/packages/dockview-core/src/dnd/droptarget.ts @@ -1,10 +1,37 @@ import { toggleClass } from '../dom'; -import { Emitter, Event } from '../events'; +import { DockviewEvent, Emitter, Event } from '../events'; import { CompositeDisposable } from '../lifecycle'; import { DragAndDropObserver } from './dnd'; import { clamp } from '../math'; import { Direction } from '../gridview/baseComponentGridview'; +export interface DroptargetEvent { + readonly position: Position; + readonly nativeEvent: DragEvent; +} + +export class WillShowOverlayEvent + extends DockviewEvent + implements DroptargetEvent +{ + get nativeEvent(): DragEvent { + return this.options.nativeEvent; + } + + get position(): Position { + return this.options.position; + } + + constructor( + private readonly options: { + nativeEvent: DragEvent; + position: Position; + } + ) { + super(); + } +} + export function directionToPosition(direction: Direction): Position { switch (direction) { case 'above': @@ -39,11 +66,6 @@ export function positionToDirection(position: Position): Direction { } } -export interface DroptargetEvent { - readonly position: Position; - readonly nativeEvent: DragEvent; -} - export type Position = 'top' | 'bottom' | 'left' | 'right' | 'center'; export type CanDisplayOverlay = @@ -70,6 +92,12 @@ const DEFAULT_SIZE: MeasuredValue = { const SMALL_WIDTH_BOUNDARY = 100; const SMALL_HEIGHT_BOUNDARY = 100; +export interface DroptargetOptions { + canDisplayOverlay: CanDisplayOverlay; + acceptedTargetZones: Position[]; + overlayModel?: DroptargetOverlayModel; +} + export class Droptarget extends CompositeDisposable { private targetElement: HTMLElement | undefined; private overlayElement: HTMLElement | undefined; @@ -79,6 +107,10 @@ export class Droptarget extends CompositeDisposable { private readonly _onDrop = new Emitter(); readonly onDrop: Event = this._onDrop.event; + private readonly _onWillShowOverlay = new Emitter(); + readonly onWillShowOverlay: Event = + this._onWillShowOverlay.event; + readonly dnd: DragAndDropObserver; private static USED_EVENT_ID = '__dockview_droptarget_event_is_used__'; @@ -89,11 +121,7 @@ export class Droptarget extends CompositeDisposable { constructor( private readonly element: HTMLElement, - private readonly options: { - canDisplayOverlay: CanDisplayOverlay; - acceptedTargetZones: Position[]; - overlayModel?: DroptargetOverlayModel; - } + private readonly options: DroptargetOptions ) { super(); @@ -142,6 +170,22 @@ export class Droptarget extends CompositeDisposable { return; } + const willShowOverlayEvent = new WillShowOverlayEvent({ + nativeEvent: e, + position: quadrant, + }); + + /** + * Provide an opportunity to prevent the overlay appearing and in turn + * any dnd behaviours + */ + this._onWillShowOverlay.fire(willShowOverlayEvent); + + if (willShowOverlayEvent.defaultPrevented) { + this.removeDropTarget(); + return; + } + if (typeof this.options.canDisplayOverlay === 'boolean') { if (!this.options.canDisplayOverlay) { this.removeDropTarget(); @@ -192,7 +236,7 @@ export class Droptarget extends CompositeDisposable { }, }); - this.addDisposables(this._onDrop, this.dnd); + this.addDisposables(this._onDrop, this._onWillShowOverlay, this.dnd); } setTargetZones(acceptedTargetZones: Position[]): void { diff --git a/packages/dockview-core/src/dockview/components/tab/tab.ts b/packages/dockview-core/src/dockview/components/tab/tab.ts index b26c754c7..946da58b8 100644 --- a/packages/dockview-core/src/dockview/components/tab/tab.ts +++ b/packages/dockview-core/src/dockview/components/tab/tab.ts @@ -9,7 +9,12 @@ import { toggleClass } from '../../../dom'; import { DockviewComponent } from '../../dockviewComponent'; import { ITabRenderer } from '../../types'; import { DockviewGroupPanel } from '../../dockviewGroupPanel'; -import { DroptargetEvent, Droptarget } from '../../../dnd/droptarget'; +import { + DroptargetEvent, + Droptarget, + Position, + WillShowOverlayEvent, +} from '../../../dnd/droptarget'; import { DragHandler } from '../../../dnd/abstractDragHandler'; import { IDockviewPanel } from '../../dockviewPanel'; @@ -40,18 +45,9 @@ class TabDragHandler extends DragHandler { } } -export interface ITab extends IDisposable { - readonly panel: IDockviewPanel; - readonly element: HTMLElement; - setContent: (element: ITabRenderer) => void; - onChanged: Event; - onDrop: Event; - setActive(isActive: boolean): void; -} - -export class Tab extends CompositeDisposable implements ITab { +export class Tab extends CompositeDisposable { private readonly _element: HTMLElement; - private readonly droptarget: Droptarget; + private readonly dropTarget: Droptarget; private content: ITabRenderer | undefined = undefined; private readonly _onChanged = new Emitter(); @@ -63,6 +59,8 @@ export class Tab extends CompositeDisposable implements ITab { private readonly _onDragStart = new Emitter(); readonly onDragStart = this._onDragStart.event; + readonly onWillShowOverlay: Event; + public get element(): HTMLElement { return this._element; } @@ -88,7 +86,7 @@ export class Tab extends CompositeDisposable implements ITab { this.panel ); - this.droptarget = new Droptarget(this._element, { + this.dropTarget = new Droptarget(this._element, { acceptedTargetZones: ['center'], canDisplayOverlay: (event, position) => { if (this.group.locked) { @@ -117,6 +115,8 @@ export class Tab extends CompositeDisposable implements ITab { }, }); + this.onWillShowOverlay = this.dropTarget.onWillShowOverlay; + this.addDisposables( this._onChanged, this._onDropped, @@ -132,10 +132,10 @@ export class Tab extends CompositeDisposable implements ITab { this._onChanged.fire(event); }), - this.droptarget.onDrop((event) => { + this.dropTarget.onDrop((event) => { this._onDropped.fire(event); }), - this.droptarget + this.dropTarget ); } diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts index 123608573..d506bd3c6 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts @@ -4,12 +4,14 @@ import { IValueDisposable, } from '../../../lifecycle'; import { addDisposableListener, Emitter, Event } from '../../../events'; -import { ITab, Tab } from '../tab/tab'; -import { DockviewComponent } from '../../dockviewComponent'; +import { Tab } from '../tab/tab'; import { DockviewGroupPanel } from '../../dockviewGroupPanel'; import { VoidContainer } from './voidContainer'; import { toggleClass } from '../../../dom'; import { DockviewPanel, IDockviewPanel } from '../../dockviewPanel'; +import { DockviewComponent } from '../../dockviewComponent'; +import { WillShowOverlayEvent } from '../../../dnd/droptarget'; +import { DockviewGroupDropLocation } from '../../dockviewGroupPanelModel'; export interface TabDropIndexEvent { readonly event: DragEvent; @@ -30,17 +32,21 @@ export interface ITabsContainer extends IDisposable { readonly element: HTMLElement; readonly panels: string[]; readonly size: number; + readonly onDrop: Event; + readonly onTabDragStart: Event; + readonly onGroupDragStart: Event; + readonly onWillShowOverlay: Event<{ + event: WillShowOverlayEvent; + kind: DockviewGroupDropLocation; + }>; hidden: boolean; - delete: (id: string) => void; - indexOf: (id: string) => number; - onDrop: Event; - onTabDragStart: Event; - onGroupDragStart: Event; - setActive: (isGroupActive: boolean) => void; - setActivePanel: (panel: IDockviewPanel) => void; - isActive: (tab: ITab) => boolean; - closePanel: (panel: IDockviewPanel) => void; - openPanel: (panel: IDockviewPanel, index?: number) => void; + delete(id: string): void; + indexOf(id: string): number; + setActive(isGroupActive: boolean): void; + setActivePanel(panel: IDockviewPanel): void; + isActive(tab: Tab): boolean; + closePanel(panel: IDockviewPanel): void; + openPanel(panel: IDockviewPanel, index?: number): void; setRightActionsElement(element: HTMLElement | undefined): void; setLeftActionsElement(element: HTMLElement | undefined): void; setPrefixActionsElement(element: HTMLElement | undefined): void; @@ -59,7 +65,7 @@ export class TabsContainer private readonly preActionsContainer: HTMLElement; private readonly voidContainer: VoidContainer; - private tabs: IValueDisposable[] = []; + private tabs: IValueDisposable[] = []; private selectedIndex = -1; private rightActions: HTMLElement | undefined; private leftActions: HTMLElement | undefined; @@ -77,6 +83,15 @@ export class TabsContainer readonly onGroupDragStart: Event = this._onGroupDragStart.event; + private readonly _onWillShowOverlay = new Emitter<{ + event: WillShowOverlayEvent; + kind: DockviewGroupDropLocation; + }>(); + readonly onWillShowOverlay: Event<{ + event: WillShowOverlayEvent; + kind: DockviewGroupDropLocation; + }> = this._onWillShowOverlay.event; + get panels(): string[] { return this.tabs.map((_) => _.value.panel.id); } @@ -150,7 +165,7 @@ export class TabsContainer return this._element; } - public isActive(tab: ITab): boolean { + public isActive(tab: Tab): boolean { return ( this.selectedIndex > -1 && this.tabs[this.selectedIndex].value === tab @@ -167,12 +182,6 @@ export class TabsContainer ) { super(); - this.addDisposables( - this._onDrop, - this._onTabDragStart, - this._onGroupDragStart - ); - this._element = document.createElement('div'); this._element.className = 'tabs-and-actions-container'; @@ -182,27 +191,6 @@ export class TabsContainer this.accessor.options.singleTabMode === 'fullwidth' ); - this.addDisposables( - this.accessor.onDidAddPanel((e) => { - if (e.api.group === this.group) { - toggleClass( - this._element, - 'dv-single-tab', - this.size === 1 - ); - } - }), - this.accessor.onDidRemovePanel((e) => { - if (e.api.group === this.group) { - toggleClass( - this._element, - 'dv-single-tab', - this.size === 1 - ); - } - }) - ); - this.rightActionsContainer = document.createElement('div'); this.rightActionsContainer.className = 'right-actions-container'; @@ -224,6 +212,28 @@ export class TabsContainer this._element.appendChild(this.rightActionsContainer); this.addDisposables( + this.accessor.onDidAddPanel((e) => { + if (e.api.group === this.group) { + toggleClass( + this._element, + 'dv-single-tab', + this.size === 1 + ); + } + }), + this.accessor.onDidRemovePanel((e) => { + if (e.api.group === this.group) { + toggleClass( + this._element, + 'dv-single-tab', + this.size === 1 + ); + } + }), + this._onWillShowOverlay, + this._onDrop, + this._onTabDragStart, + this._onGroupDragStart, this.voidContainer, this.voidContainer.onDragStart((event) => { this._onGroupDragStart.fire({ @@ -237,6 +247,9 @@ export class TabsContainer index: this.tabs.length, }); }), + this.voidContainer.onWillShowOverlay((event) => { + this._onWillShowOverlay.fire({ event, kind: 'header_space' }); + }), addDisposableListener( this.voidContainer.element, 'mousedown', @@ -286,7 +299,7 @@ export class TabsContainer } private addTab( - tab: IValueDisposable, + tab: IValueDisposable, index: number = this.tabs.length ): void { if (index < 0 || index > this.tabs.length) { @@ -395,10 +408,13 @@ export class TabsContainer event: event.nativeEvent, index: this.tabs.findIndex((x) => x.value === tab), }); + }), + tab.onWillShowOverlay((event) => { + this._onWillShowOverlay.fire({ event, kind: 'tab' }); }) ); - const value: IValueDisposable = { value: tab, disposable }; + const value: IValueDisposable = { value: tab, disposable }; this.addTab(value, index); } diff --git a/packages/dockview-core/src/dockview/components/titlebar/voidContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/voidContainer.ts index 659a1d3a8..3ab3652f4 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/voidContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/voidContainer.ts @@ -1,6 +1,10 @@ import { last } from '../../../array'; import { getPanelData } from '../../../dnd/dataTransfer'; -import { Droptarget, DroptargetEvent } from '../../../dnd/droptarget'; +import { + Droptarget, + DroptargetEvent, + WillShowOverlayEvent, +} from '../../../dnd/droptarget'; import { GroupDragHandler } from '../../../dnd/groupDragHandler'; import { DockviewComponent } from '../../dockviewComponent'; import { addDisposableListener, Emitter, Event } from '../../../events'; @@ -9,7 +13,7 @@ import { DockviewGroupPanel } from '../../dockviewGroupPanel'; export class VoidContainer extends CompositeDisposable { private readonly _element: HTMLElement; - private readonly voidDropTarget: Droptarget; + private readonly dropTraget: Droptarget; private readonly _onDrop = new Emitter(); readonly onDrop: Event = this._onDrop.event; @@ -17,6 +21,8 @@ export class VoidContainer extends CompositeDisposable { private readonly _onDragStart = new Emitter(); readonly onDragStart = this._onDragStart.event; + readonly onWillShowOverlay: Event; + get element(): HTMLElement { return this._element; } @@ -43,7 +49,7 @@ export class VoidContainer extends CompositeDisposable { const handler = new GroupDragHandler(this._element, accessor, group); - this.voidDropTarget = new Droptarget(this._element, { + this.dropTraget = new Droptarget(this._element, { acceptedTargetZones: ['center'], canDisplayOverlay: (event, position) => { const data = getPanelData(); @@ -65,15 +71,17 @@ export class VoidContainer extends CompositeDisposable { }, }); + this.onWillShowOverlay = this.dropTraget.onWillShowOverlay; + this.addDisposables( handler, handler.onDragStart((event) => { this._onDragStart.fire(event); }), - this.voidDropTarget.onDrop((event) => { + this.dropTraget.onDrop((event) => { this._onDrop.fire(event); }), - this.voidDropTarget + this.dropTraget ); } } diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index d71bfa1dd..adb15cf95 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -40,7 +40,9 @@ import { Orientation, Sizing } from '../splitview/splitview'; import { GroupOptions, GroupPanelViewState, - GroupviewDropEvent, + DockviewDidDropEvent, + DockviewWillDropEvent, + WillShowOverlayLocationEvent, } from './dockviewGroupPanelModel'; import { DockviewGroupPanel } from './dockviewGroupPanel'; import { DockviewPanelModel } from './dockviewPanelModel'; @@ -226,18 +228,16 @@ export type DockviewComponentUpdateOptions = Pick< | 'disableFloatingGroups' | 'floatingGroupBounds' | 'rootOverlayModel' + | 'disableDnd' >; -export interface DockviewDropEvent extends GroupviewDropEvent { - api: DockviewApi; - group: DockviewGroupPanel | null; -} - export interface IDockviewComponent extends IBaseGrid { readonly activePanel: IDockviewPanel | undefined; readonly totalPanels: number; readonly panels: IDockviewPanel[]; - readonly onDidDrop: Event; + readonly onDidDrop: Event; + readonly onWillDrop: Event; + readonly onWillShowOverlay: Event; readonly orientation: Orientation; updateOptions(options: DockviewComponentUpdateOptions): void; moveGroupOrPanel( @@ -305,8 +305,16 @@ export class DockviewComponent readonly onWillDragGroup: Event = this._onWillDragGroup.event; - private readonly _onDidDrop = new Emitter(); - readonly onDidDrop: Event = this._onDidDrop.event; + private readonly _onDidDrop = new Emitter(); + readonly onDidDrop: Event = this._onDidDrop.event; + + private readonly _onWillDrop = new Emitter(); + readonly onWillDrop: Event = this._onWillDrop.event; + + private readonly _onWillShowOverlay = + new Emitter(); + readonly onWillShowOverlay: Event = + this._onWillShowOverlay.event; private readonly _onDidRemovePanel = new Emitter(); readonly onDidRemovePanel: Event = @@ -380,11 +388,13 @@ export class DockviewComponent this.overlayRenderContainer, this._onWillDragPanel, this._onWillDragGroup, + this._onWillShowOverlay, this._onDidActivePanelChange, this._onDidAddPanel, this._onDidRemovePanel, this._onDidLayoutFromJSON, this._onDidDrop, + this._onWillDrop, Event.any( this.onDidAddGroup, this.onDidRemoveGroup @@ -477,6 +487,22 @@ export class DockviewComponent this.addDisposables( this._rootDropTarget.onDrop((event) => { + const willDropEvent = new DockviewWillDropEvent({ + nativeEvent: event.nativeEvent, + position: event.position, + panel: undefined, + api: this._api, + group: undefined, + getData: getPanelData, + kind: 'content', + }); + + this._onWillDrop.fire(willDropEvent); + + if (willDropEvent.defaultPrevented) { + return; + } + const data = getPanelData(); if (data) { @@ -487,12 +513,16 @@ export class DockviewComponent 'center' ); } else { - this._onDidDrop.fire({ - ...event, - api: this._api, - group: null, - getData: getPanelData, - }); + this._onDidDrop.fire( + new DockviewDidDropEvent({ + nativeEvent: event.nativeEvent, + position: event.position, + panel: undefined, + api: this._api, + group: undefined, + getData: getPanelData, + }) + ); } }), this._rootDropTarget @@ -1652,11 +1682,18 @@ export class DockviewComponent this.moveGroupOrPanel(view, groupId, itemId, target, index); }), view.model.onDidDrop((event) => { - this._onDidDrop.fire({ - ...event, - api: this._api, - group: view, - }); + this._onDidDrop.fire(event); + }), + view.model.onWillDrop((event) => { + this._onWillDrop.fire(event); + }), + view.model.onWillShowOverlay((event) => { + if (this.options.disableDnd) { + event.event.preventDefault(); + return; + } + + this._onWillShowOverlay.fire(event); }), view.model.onDidAddPanel((event) => { this._onDidAddPanel.fire(event.panel); diff --git a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts index 6837fe618..5b741c3c4 100644 --- a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts +++ b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts @@ -1,9 +1,14 @@ import { DockviewApi } from '../api/component.api'; import { getPanelData, PanelTransfer } from '../dnd/dataTransfer'; -import { Position } from '../dnd/droptarget'; +import { Position, WillShowOverlayEvent } from '../dnd/droptarget'; import { DockviewComponent } from './dockviewComponent'; import { isAncestor, toggleClass } from '../dom'; -import { addDisposableListener, Emitter, Event } from '../events'; +import { + addDisposableListener, + DockviewEvent, + Emitter, + Event, +} from '../events'; import { IViewSize } from '../gridview/gridview'; import { CompositeDisposable } from '../lifecycle'; import { IPanel, PanelInitParameters, PanelUpdateEvent } from '../panel/types'; @@ -46,15 +51,69 @@ export interface GroupPanelViewState extends CoreGroupOptions { id: string; } -export interface GroupviewChangeEvent { +export interface DockviewGroupChangeEvent { readonly panel: IDockviewPanel; } -export interface GroupviewDropEvent { - readonly nativeEvent: DragEvent; - readonly position: Position; - readonly index?: number; - getData(): PanelTransfer | undefined; +export class DockviewDidDropEvent extends DockviewEvent { + get nativeEvent(): DragEvent { + return this.options.nativeEvent; + } + + get position(): Position { + return this.options.position; + } + + get panel(): IDockviewPanel | undefined { + return this.options.panel; + } + + get group(): DockviewGroupPanel | undefined { + return this.options.group; + } + + get api(): DockviewApi { + return this.options.api; + } + + constructor( + private readonly options: { + readonly nativeEvent: DragEvent; + readonly position: Position; + readonly panel?: IDockviewPanel; + getData(): PanelTransfer | undefined; + group?: DockviewGroupPanel; + api: DockviewApi; + } + ) { + super(); + } + + getData(): PanelTransfer | undefined { + return this.options.getData(); + } +} + +export class DockviewWillDropEvent extends DockviewDidDropEvent { + private readonly _kind: DockviewGroupDropLocation; + + get kind(): DockviewGroupDropLocation { + return this._kind; + } + + constructor(options: { + readonly nativeEvent: DragEvent; + readonly position: Position; + readonly panel?: IDockviewPanel; + getData(): PanelTransfer | undefined; + kind: DockviewGroupDropLocation; + group?: DockviewGroupPanel; + api: DockviewApi; + }) { + super(options); + + this._kind = options.kind; + } } export interface IHeader { @@ -63,6 +122,8 @@ export interface IHeader { export type DockviewGroupPanelLocked = boolean | 'no-drop-target'; +export type DockviewGroupDropLocation = 'tab' | 'header_space' | 'content'; + export interface IDockviewGroupPanelModel extends IPanel { readonly isActive: boolean; readonly size: number; @@ -70,10 +131,11 @@ export interface IDockviewGroupPanelModel extends IPanel { readonly activePanel: IDockviewPanel | undefined; readonly header: IHeader; readonly isContentFocused: boolean; - readonly onDidDrop: Event; - readonly onDidAddPanel: Event; - readonly onDidRemovePanel: Event; - readonly onDidActivePanelChange: Event; + readonly onDidDrop: Event; + readonly onWillDrop: Event; + readonly onDidAddPanel: Event; + readonly onDidRemovePanel: Event; + readonly onDidActivePanelChange: Event; readonly onMove: Event; locked: DockviewGroupPanelLocked; setActive(isActive: boolean): void; @@ -112,13 +174,17 @@ export interface IDockviewGroupPanelModel extends IPanel { export type DockviewGroupLocation = 'grid' | 'floating' | 'popout'; +export interface WillShowOverlayLocationEvent { + event: WillShowOverlayEvent; + kind: DockviewGroupDropLocation; +} + export class DockviewGroupPanelModel extends CompositeDisposable implements IDockviewGroupPanelModel { private readonly tabsContainer: ITabsContainer; private readonly contentContainer: IContentContainer; - // private readonly dropTarget: Droptarget; private _activePanel: IDockviewPanel | undefined; private watermark?: IWatermarkRenderer; private _isGroupActive = false; @@ -143,8 +209,16 @@ export class DockviewGroupPanelModel private readonly _onMove = new Emitter(); readonly onMove: Event = this._onMove.event; - private readonly _onDidDrop = new Emitter(); - readonly onDidDrop: Event = this._onDidDrop.event; + private readonly _onDidDrop = new Emitter(); + readonly onDidDrop: Event = this._onDidDrop.event; + + private readonly _onWillDrop = new Emitter(); + readonly onWillDrop: Event = this._onWillDrop.event; + + private readonly _onWillShowOverlay = + new Emitter(); + readonly onWillShowOverlay: Event = + this._onWillShowOverlay.event; private readonly _onTabDragStart = new Emitter(); readonly onTabDragStart: Event = this._onTabDragStart.event; @@ -153,19 +227,22 @@ export class DockviewGroupPanelModel readonly onGroupDragStart: Event = this._onGroupDragStart.event; - private readonly _onDidAddPanel = new Emitter(); - readonly onDidAddPanel: Event = + private readonly _onDidAddPanel = new Emitter(); + readonly onDidAddPanel: Event = this._onDidAddPanel.event; - private readonly _onDidRemovePanel = new Emitter(); - readonly onDidRemovePanel: Event = + private readonly _onDidRemovePanel = + new Emitter(); + readonly onDidRemovePanel: Event = this._onDidRemovePanel.event; private readonly _onDidActivePanelChange = - new Emitter(); - readonly onDidActivePanelChange: Event = + new Emitter(); + readonly onDidActivePanelChange: Event = this._onDidActivePanelChange.event; + private readonly _api: DockviewApi; + get element(): HTMLElement { throw new Error('not supported'); } @@ -279,6 +356,8 @@ export class DockviewGroupPanelModel toggleClass(this.container, 'groupview', true); + this._api = new DockviewApi(this.accessor); + this.tabsContainer = new TabsContainer(this.accessor, this.groupPanel); this.contentContainer = new ContentContainer(this.accessor, this); @@ -294,6 +373,7 @@ export class DockviewGroupPanelModel this.addDisposables( this._onTabDragStart, this._onGroupDragStart, + this._onWillShowOverlay, this.tabsContainer.onTabDragStart((event) => { this._onTabDragStart.fire(event); }), @@ -308,6 +388,7 @@ export class DockviewGroupPanelModel event.index ); }), + this.contentContainer.onDidFocus(() => { this.accessor.doSetGroupActive(this.groupPanel, true); }), @@ -321,9 +402,16 @@ export class DockviewGroupPanelModel event.position ); }), + this.tabsContainer.onWillShowOverlay((event) => { + this._onWillShowOverlay.fire(event); + }), + this.contentContainer.dropTarget.onWillShowOverlay((event) => { + this._onWillShowOverlay.fire({ event, kind: 'content' }); + }), this._onMove, this._onDidChange, this._onDidDrop, + this._onWillDrop, this._onDidAddPanel, this._onDidRemovePanel, this._onDidActivePanelChange @@ -331,13 +419,13 @@ export class DockviewGroupPanelModel } initialize(): void { - if (this.options?.panels) { + if (this.options.panels) { this.options.panels.forEach((panel) => { this.doAddPanel(panel); }); } - if (this.options?.activePanel) { + if (this.options.activePanel) { this.openPanel(this.options.activePanel); } @@ -353,7 +441,7 @@ export class DockviewGroupPanelModel ); this.addDisposables(this._rightHeaderActions); this._rightHeaderActions.init({ - containerApi: new DockviewApi(this.accessor), + containerApi: this._api, api: this.groupPanel.api, }); this.tabsContainer.setRightActionsElement( @@ -368,7 +456,7 @@ export class DockviewGroupPanelModel ); this.addDisposables(this._leftHeaderActions); this._leftHeaderActions.init({ - containerApi: new DockviewApi(this.accessor), + containerApi: this._api, api: this.groupPanel.api, }); this.tabsContainer.setLeftActionsElement( @@ -383,7 +471,7 @@ export class DockviewGroupPanelModel ); this.addDisposables(this._prefixHeaderActions); this._prefixHeaderActions.init({ - containerApi: new DockviewApi(this.accessor), + containerApi: this._api, api: this.groupPanel.api, }); this.tabsContainer.setPrefixActionsElement( @@ -721,7 +809,7 @@ export class DockviewGroupPanelModel if (this.isEmpty && !this.watermark) { const watermark = this.accessor.createWatermarkComponent(); watermark.init({ - containerApi: new DockviewApi(this.accessor), + containerApi: this._api, group: this.groupPanel, }); this.watermark = watermark; @@ -773,7 +861,7 @@ export class DockviewGroupPanelModel return; } - function getKind(): 'tab' | 'header_space' | 'content' { + function getKind(): DockviewGroupDropLocation { switch (type) { case 'header': return typeof index === 'number' ? 'tab' : 'header_space'; @@ -782,7 +870,24 @@ export class DockviewGroupPanelModel } } - const kind = getKind(); + const panel = + typeof index === 'number' ? this.panels[index] : undefined; + + const willDropEvent = new DockviewWillDropEvent({ + nativeEvent: event, + position, + panel, + getData: () => getPanelData(), + kind: getKind(), + group: this.groupPanel, + api: this._api, + }); + + this._onWillDrop.fire(willDropEvent); + + if (willDropEvent.defaultPrevented) { + return; + } const data = getPanelData(); @@ -791,7 +896,6 @@ export class DockviewGroupPanelModel // this is a group move dnd event const { groupId } = data; - // TODO: intercept this._onMove.fire({ target: position, groupId: groupId, @@ -816,7 +920,6 @@ export class DockviewGroupPanelModel } } - // TODO: intercept this._onMove.fire({ target: position, groupId: data.groupId, @@ -824,12 +927,16 @@ export class DockviewGroupPanelModel index, }); } else { - this._onDidDrop.fire({ - nativeEvent: event, - position, - index, - getData: () => getPanelData(), - }); + this._onDidDrop.fire( + new DockviewDidDropEvent({ + nativeEvent: event, + position, + panel, + getData: () => getPanelData(), + group: this.groupPanel, + api: this._api, + }) + ); } } diff --git a/packages/dockview-core/src/dockview/options.ts b/packages/dockview-core/src/dockview/options.ts index 34ab98989..78514dfe7 100644 --- a/packages/dockview-core/src/dockview/options.ts +++ b/packages/dockview-core/src/dockview/options.ts @@ -101,6 +101,7 @@ export interface DockviewComponentOptions extends DockviewRenderFunctions { defaultRenderer?: DockviewPanelRenderer; debug?: boolean; rootOverlayModel?: DroptargetOverlayModel; + disableDnd?: boolean; } export interface PanelOptions

{ diff --git a/packages/dockview-core/src/events.ts b/packages/dockview-core/src/events.ts index 9474ad317..27c515460 100644 --- a/packages/dockview-core/src/events.ts +++ b/packages/dockview-core/src/events.ts @@ -24,6 +24,18 @@ export namespace Event { }; } +export class DockviewEvent { + private _defaultPrevented = false; + + get defaultPrevented(): boolean { + return this._defaultPrevented; + } + + preventDefault(): void { + this._defaultPrevented = true; + } +} + class LeakageMonitor { readonly events = new Map, Stacktrace>(); diff --git a/packages/dockview/src/dockview/dockview.tsx b/packages/dockview/src/dockview/dockview.tsx index 91aaa0562..2289e107f 100644 --- a/packages/dockview/src/dockview/dockview.tsx +++ b/packages/dockview/src/dockview/dockview.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { DockviewComponent, - DockviewDropEvent, + DockviewWillDropEvent, DockviewDndOverlayEvent, GroupPanelFrameworkComponentFactory, DockviewPanelApi, @@ -12,6 +12,7 @@ import { IHeaderActionsRenderer, DockviewPanelRenderer, DroptargetOverlayModel, + DockviewDidDropEvent, } from 'dockview-core'; import { ReactPanelContentPart } from './reactContentPart'; import { ReactPanelHeaderPart } from './reactHeaderPart'; @@ -61,7 +62,8 @@ export interface IDockviewReactProps { components: PanelCollection; tabComponents?: PanelCollection; watermarkComponent?: React.FunctionComponent; - onDidDrop?: (event: DockviewDropEvent) => void; + onDidDrop?: (event: DockviewDidDropEvent) => void; + onWillDrop?: (event: DockviewWillDropEvent) => void; showDndOverlay?: (event: DockviewDndOverlayEvent) => boolean; hideBorders?: boolean; className?: string; @@ -81,6 +83,7 @@ export interface IDockviewReactProps { debug?: boolean; defaultRenderer?: DockviewPanelRenderer; rootOverlayModel?: DroptargetOverlayModel; + disableDnd?: boolean; } const DEFAULT_REACT_TAB = 'props.defaultTabComponent'; @@ -183,6 +186,7 @@ export const DockviewReact = React.forwardRef( defaultRenderer: props.defaultRenderer, debug: props.debug, rootOverlayModel: props.rootOverlayModel, + disableDnd: props.disableDnd, }); const { clientWidth, clientHeight } = domRef.current; @@ -199,6 +203,15 @@ export const DockviewReact = React.forwardRef( }; }, []); + React.useEffect(() => { + if (!dockviewRef.current) { + return; + } + dockviewRef.current.updateOptions({ + disableDnd: props.disableDnd, + }); + }, [props.disableDnd]); + React.useEffect(() => { if (!dockviewRef.current) { return () => { @@ -217,6 +230,24 @@ export const DockviewReact = React.forwardRef( }; }, [props.onDidDrop]); + React.useEffect(() => { + if (!dockviewRef.current) { + return () => { + // noop + }; + } + + const disposable = dockviewRef.current.onWillDrop((event) => { + if (props.onWillDrop) { + props.onWillDrop(event); + } + }); + + return () => { + disposable.dispose(); + }; + }, [props.onWillDrop]); + React.useEffect(() => { if (!dockviewRef.current) { return; From 0bca63b550dd66fd7922afaabb4b39d5d300752f Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Tue, 30 Jan 2024 17:41:04 +0000 Subject: [PATCH 3/5] feat: popout group enhancements --- .../dockview/components/panel/content.spec.ts | 39 ++++++----- .../dockview/dockviewComponent.spec.ts | 67 ++++++++++--------- .../src/dockview/components/panel/content.ts | 4 +- .../src/dockview/dockviewComponent.ts | 45 +++++++++++-- .../src/dockview/dockviewGroupPanelModel.ts | 22 ++++++ 5 files changed, 121 insertions(+), 56 deletions(-) diff --git a/packages/dockview-core/src/__tests__/dockview/components/panel/content.spec.ts b/packages/dockview-core/src/__tests__/dockview/components/panel/content.spec.ts index eacad7b9a..670c981d8 100644 --- a/packages/dockview-core/src/__tests__/dockview/components/panel/content.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/components/panel/content.spec.ts @@ -62,16 +62,19 @@ describe('contentContainer', () => { const disposable = new CompositeDisposable(); - const dockviewComponent = jest.fn(() => { - return { - renderer: 'onlyWhenVisibile', - overlayRenderContainer: new OverlayRenderContainer( - document.createElement('div') - ), - } as DockviewComponent; - }); + const overlayRenderContainer = new OverlayRenderContainer( + document.createElement('div') + ); - const cut = new ContentContainer(dockviewComponent(), jest.fn() as any); + const cut = new ContentContainer( + fromPartial({ + renderer: 'onlyWhenVisibile', + overlayRenderContainer, + }), + fromPartial({ + renderContainer: overlayRenderContainer, + }) + ); disposable.addDisposables( cut.onDidFocus(() => { @@ -84,12 +87,12 @@ describe('contentContainer', () => { const contentRenderer = new TestContentRenderer('id-1'); - const panel = { + const panel = fromPartial({ view: { content: contentRenderer, - } as Partial, + }, api: { renderer: 'onlyWhenVisibile' }, - } as Partial; + }); cut.openPanel(panel as IDockviewPanel); @@ -151,13 +154,17 @@ describe('contentContainer', () => { }); test("that panels renderered as 'onlyWhenVisibile' are removed when closed", () => { + const overlayRenderContainer = fromPartial({ + detatch: jest.fn(), + }); + const cut = new ContentContainer( fromPartial({ - overlayRenderContainer: { - detatch: jest.fn(), - }, + overlayRenderContainer, }), - fromPartial({}) + fromPartial({ + renderContainer: overlayRenderContainer, + }) ); const panel1 = fromPartial({ diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index 49c5ff221..51482f0ed 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -4434,44 +4434,12 @@ describe('dockviewComponent', () => { cb(); } }), + removeEventListener: jest.fn(), close: jest.fn(), }) ); }); - test('that can remove a popout group', async () => { - const container = document.createElement('div'); - - const dockview = new DockviewComponent({ - parentElement: container, - components: { - default: PanelContentPartTest, - }, - tabComponents: { - test_tab_id: PanelTabPartTest, - }, - orientation: Orientation.HORIZONTAL, - }); - - dockview.layout(1000, 500); - - const panel1 = dockview.addPanel({ - id: 'panel_1', - component: 'default', - }); - - await dockview.addPopoutGroup(panel1); - - expect(dockview.panels.length).toBe(1); - expect(dockview.groups.length).toBe(2); - expect(panel1.api.group.api.location.type).toBe('popout'); - - dockview.removePanel(panel1); - - expect(dockview.panels.length).toBe(0); - expect(dockview.groups.length).toBe(0); - }); - test('add a popout group', async () => { const container = document.createElement('div'); @@ -4511,6 +4479,39 @@ describe('dockviewComponent', () => { expect(dockview.panels.length).toBe(2); }); + test('that can remove a popout group', async () => { + const container = document.createElement('div'); + + const dockview = new DockviewComponent({ + parentElement: container, + components: { + default: PanelContentPartTest, + }, + tabComponents: { + test_tab_id: PanelTabPartTest, + }, + orientation: Orientation.HORIZONTAL, + }); + + dockview.layout(1000, 500); + + const panel1 = dockview.addPanel({ + id: 'panel_1', + component: 'default', + }); + + await dockview.addPopoutGroup(panel1); + + expect(dockview.panels.length).toBe(1); + expect(dockview.groups.length).toBe(2); + expect(panel1.api.group.api.location.type).toBe('popout'); + + dockview.removePanel(panel1); + + expect(dockview.panels.length).toBe(0); + expect(dockview.groups.length).toBe(0); + }); + test('move from fixed to popout group and back', async () => { const container = document.createElement('div'); diff --git a/packages/dockview-core/src/dockview/components/panel/content.ts b/packages/dockview-core/src/dockview/components/panel/content.ts index 9d9e3f2ca..933415283 100644 --- a/packages/dockview-core/src/dockview/components/panel/content.ts +++ b/packages/dockview-core/src/dockview/components/panel/content.ts @@ -133,7 +133,7 @@ export class ContentContainer switch (panel.api.renderer) { case 'onlyWhenVisibile': - this.accessor.overlayRenderContainer.detatch(panel); + this.group.renderContainer.detatch(panel); if (this.panel) { if (doRender) { this._element.appendChild( @@ -149,7 +149,7 @@ export class ContentContainer ) { this._element.removeChild(panel.view.content.element); } - container = this.accessor.overlayRenderContainer.attach({ + container = this.group.renderContainer.attach({ panel, referenceContainer: this, }); diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 400205cc6..926abd40c 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -13,7 +13,7 @@ import { import { tail, sequenceEquals, remove } from '../array'; import { DockviewPanel, IDockviewPanel } from './dockviewPanel'; import { CompositeDisposable, Disposable, IDisposable } from '../lifecycle'; -import { Event, Emitter } from '../events'; +import { Event, Emitter, addDisposableWindowListener } from '../events'; import { Watermark } from './components/watermark/watermark'; import { IWatermarkRenderer, GroupviewPanelState } from './types'; import { sequentialNumberGenerator } from '../math'; @@ -561,12 +561,15 @@ export class DockviewComponent from: DockviewGroupPanel; to: DockviewGroupPanel; }) { + const activePanel = options.from.activePanel; const panels = [...options.from.panels].map((panel) => options.from.model.removePanel(panel) ); panels.forEach((panel) => { - options.to.model.openPanel(panel); + options.to.model.openPanel(panel, { + skipSetPanelActive: activePanel !== panel, + }); }); } @@ -624,10 +627,18 @@ export class DockviewComponent return; } + const gready = document.createElement('div'); + gready.className = 'dv-overlay-render-container'; + + const overlayRenderContainer = new OverlayRenderContainer( + gready + ); + const referenceGroup = item instanceof DockviewPanel ? item.group : item; const group = this.createGroup({ id: groupId }); + group.model.renderContainer = overlayRenderContainer; if (item instanceof DockviewPanel) { const panel = referenceGroup.model.removePanel(item); @@ -637,9 +648,13 @@ export class DockviewComponent from: referenceGroup, to: group, }); - referenceGroup.api.setHidden(false); + referenceGroup.api.setHidden(true); } + popoutContainer.classList.add('dv-dockview'); + popoutContainer.style.overflow = 'hidden'; + popoutContainer.appendChild(gready); + popoutContainer.appendChild(group.element); group.model.location = { @@ -655,6 +670,19 @@ export class DockviewComponent }; popoutWindowDisposable.addDisposables( + /** + * ResizeObserver seems slow here, I do not know why but we don't need it + * since we can reply on the window resize event as we will occupy the full + * window dimensions + */ + addDisposableWindowListener( + _window.window!, + 'resize', + () => { + group.layout(window.innerWidth, window.innerHeight); + } + ), + overlayRenderContainer, Disposable.from(() => { if (this.getPanel(referenceGroup.id)) { moveGroupWithoutDestroying({ @@ -666,12 +694,16 @@ export class DockviewComponent referenceGroup.api.setHidden(false); } - this.doRemoveGroup(group); + this.doRemoveGroup(group, { + skipPopoutAssociated: true, + }); } else { const removedGroup = this.doRemoveGroup(group, { skipDispose: true, skipActive: true, }); + removedGroup.model.renderContainer = + this.overlayRenderContainer; removedGroup.model.location = { type: 'grid' }; this.doAddGroup(removedGroup, [0]); } @@ -1484,6 +1516,7 @@ export class DockviewComponent | { skipActive?: boolean; skipDispose?: boolean; + skipPopoutAssociated?: boolean; } | undefined ): DockviewGroupPanel { @@ -1523,7 +1556,9 @@ export class DockviewComponent if (selectedGroup) { if (!options?.skipDispose) { - this.doRemoveGroup(selectedGroup.referenceGroup); + if (!options?.skipPopoutAssociated) { + this.removeGroup(selectedGroup.referenceGroup); + } selectedGroup.popoutGroup.dispose(); this._groups.delete(group.id); diff --git a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts index 44a912c55..12d548681 100644 --- a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts +++ b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts @@ -26,6 +26,7 @@ import { DockviewDropTargets, IWatermarkRenderer } from './types'; import { DockviewGroupPanel } from './dockviewGroupPanel'; import { IDockviewPanel } from './dockviewPanel'; import { IHeaderActionsRenderer } from './options'; +import { OverlayRenderContainer } from '../overlayRenderContainer'; interface GroupMoveEvent { groupId: string; @@ -421,6 +422,27 @@ export class DockviewGroupPanelModel ); } + private _overwriteRenderContainer: OverlayRenderContainer | null = null; + + set renderContainer(value: OverlayRenderContainer | null) { + this.panels.forEach((panel) => { + this.renderContainer.detatch(panel); + }); + + this._overwriteRenderContainer = value; + + this.panels.forEach((panel) => { + this.rerender(panel); + }); + } + + get renderContainer(): OverlayRenderContainer { + return ( + this._overwriteRenderContainer ?? + this.accessor.overlayRenderContainer + ); + } + initialize(): void { if (this.options.panels) { this.options.panels.forEach((panel) => { From 926b68826788b6f381897e86e02e07659f7e86c2 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Tue, 30 Jan 2024 19:41:05 +0000 Subject: [PATCH 4/5] chore: stop nightly docs job --- .github/workflows/deploy-docs.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 595869a12..14d6189cc 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -2,8 +2,6 @@ name: Deploy Docs on: workflow_dispatch: - schedule: - - cron: '0 3 * * *' # every day at 3 am UTC jobs: deploy-nightly-demo-app: From 66d96cf6ae5a7e349fc3cf8c9efaa097e5f0ecec Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Tue, 30 Jan 2024 19:43:27 +0000 Subject: [PATCH 5/5] chore: upgrade builds to node v20 --- .github/workflows/deploy-docs.yml | 2 +- .github/workflows/main.yml | 2 +- .github/workflows/publish.yml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 14d6189cc..4086652d6 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -12,7 +12,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: '18.x' + node-version: '20.x' - uses: actions/cache@v3 with: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ce489b923..e651f5912 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,7 +14,7 @@ jobs: - name: Use Node.js uses: actions/setup-node@v3 with: - node-version: '18.x' + node-version: '20.x' - uses: actions/cache@v3 with: diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 2eb2ffa1d..bb21bec62 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: - node-version: '18.x' + node-version: '20.x' registry-url: 'https://registry.npmjs.org' - uses: actions/cache@v3 @@ -46,7 +46,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: - node-version: '18.x' + node-version: '20.x' registry-url: 'https://registry.npmjs.org' - uses: actions/cache@v3