diff --git a/packages/dockview/src/__tests__/dockview/defaultGroupPanelView.spec.ts b/packages/dockview/src/__tests__/dockview/defaultGroupPanelView.spec.ts index ab91faeb4..bd5a9b75b 100644 --- a/packages/dockview/src/__tests__/dockview/defaultGroupPanelView.spec.ts +++ b/packages/dockview/src/__tests__/dockview/defaultGroupPanelView.spec.ts @@ -1,9 +1,5 @@ import { DefaultGroupPanelView } from '../../dockview/defaultGroupPanelView'; -import { - IActionsRenderer, - IContentRenderer, - ITabRenderer, -} from '../../groupview/types'; +import { IContentRenderer, ITabRenderer } from '../../groupview/types'; describe('defaultGroupPanelView', () => { test('dispose cleanup', () => { @@ -23,24 +19,14 @@ describe('defaultGroupPanelView', () => { return partial as IContentRenderer; }); - const actionsMock = jest.fn(() => { - const partial: Partial = { - element: document.createElement('div'), - dispose: jest.fn(), - }; - return partial as IContentRenderer; - }); - const content = new contentMock(); const tab = new tabMock(); - const actions = new actionsMock(); - const cut = new DefaultGroupPanelView({ content, tab, actions }); + const cut = new DefaultGroupPanelView({ content, tab }); cut.dispose(); expect(content.dispose).toHaveBeenCalled(); expect(tab.dispose).toHaveBeenCalled(); - expect(actions.dispose).toHaveBeenCalled(); }); }); diff --git a/packages/dockview/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview/src/__tests__/dockview/dockviewComponent.spec.ts index 89e50ec33..3d26a0c33 100644 --- a/packages/dockview/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview/src/__tests__/dockview/dockviewComponent.spec.ts @@ -152,7 +152,7 @@ class TestGroupPanel implements IDockviewPanel { public readonly title: string, accessor: DockviewComponent ) { - this.api = new DockviewPanelApiImpl(this, this._group); + this.api = new DockviewPanelApiImpl(this, this._group!); this._group = new GroupPanel(accessor, id, {}); this.view = new TestGroupPanelView( new PanelContentPartTest(id, 'component') @@ -162,9 +162,8 @@ class TestGroupPanel implements IDockviewPanel { get params(): Record { return {}; } - - get group(): GroupPanel | undefined { - return this._group; + get group(): GroupPanel { + return this._group!; } updateParentGroup(group: GroupPanel, isGroupActive: boolean): void { diff --git a/packages/dockview/src/__tests__/groupview/groupview.spec.ts b/packages/dockview/src/__tests__/groupview/groupview.spec.ts index 15a6bea24..c93e4c093 100644 --- a/packages/dockview/src/__tests__/groupview/groupview.spec.ts +++ b/packages/dockview/src/__tests__/groupview/groupview.spec.ts @@ -11,11 +11,7 @@ import { IWatermarkRenderer, } from '../../groupview/types'; import { PanelUpdateEvent } from '../../panel/types'; -import { - GroupChangeKind2, - GroupOptions, - Groupview, -} from '../../groupview/groupview'; +import { GroupOptions, Groupview } from '../../groupview/groupview'; import { DockviewPanelApi } from '../../api/groupPanelApi'; import { DefaultGroupPanelView, @@ -24,6 +20,13 @@ import { import { GroupPanel } from '../../groupview/groupviewPanel'; import { fireEvent } from '@testing-library/dom'; import { LocalSelectionTransfer, PanelTransfer } from '../../dnd/dataTransfer'; +import { CompositeDisposable } from '../../lifecycle'; + +enum GroupChangeKind2 { + ADD_PANEL, + REMOVE_PANEL, + PANEL_ACTIVE, +} class Watermark implements IWatermarkRenderer { public readonly element = document.createElement('div'); @@ -193,7 +196,7 @@ export class TestPanel implements IDockviewPanel { toJSON(): GroupviewPanelState { return { id: this.id, - view: this._view.toJSON(), + view: this._view?.toJSON(), title: this._params?.title, }; } @@ -269,10 +272,29 @@ describe('groupview', () => { const events: Array<{ kind: GroupChangeKind2; + panel?: IDockviewPanel; }> = []; - const disposable = groupview2.model.onDidGroupChange((e) => { - events.push(e); - }); + + const disposable = new CompositeDisposable( + groupview2.model.onDidAddPanel((e) => { + events.push({ + kind: GroupChangeKind2.ADD_PANEL, + panel: e.panel, + }); + }), + groupview2.model.onDidRemovePanel((e) => { + events.push({ + kind: GroupChangeKind2.REMOVE_PANEL, + panel: e.panel, + }); + }), + groupview2.model.onDidActivePanelChange((e) => { + events.push({ + kind: GroupChangeKind2.PANEL_ACTIVE, + panel: e.panel, + }); + }) + ); groupview2.initialize(); @@ -301,10 +323,29 @@ describe('groupview', () => { test('panel events flow', () => { let events: Array<{ kind: GroupChangeKind2; + panel?: IDockviewPanel; }> = []; - const disposable = groupview.model.onDidGroupChange((e) => { - events.push(e); - }); + + const disposable = new CompositeDisposable( + groupview.model.onDidAddPanel((e) => { + events.push({ + kind: GroupChangeKind2.ADD_PANEL, + panel: e.panel, + }); + }), + groupview.model.onDidRemovePanel((e) => { + events.push({ + kind: GroupChangeKind2.REMOVE_PANEL, + panel: e.panel, + }); + }), + groupview.model.onDidActivePanelChange((e) => { + events.push({ + kind: GroupChangeKind2.PANEL_ACTIVE, + panel: e.panel, + }); + }) + ); const panel1 = new TestPanel('panel1', jest.fn() as any); const panel2 = new TestPanel('panel2', jest.fn() as any); diff --git a/packages/dockview/src/dockview/defaultGroupPanelView.ts b/packages/dockview/src/dockview/defaultGroupPanelView.ts index c59190758..dd15b734e 100644 --- a/packages/dockview/src/dockview/defaultGroupPanelView.ts +++ b/packages/dockview/src/dockview/defaultGroupPanelView.ts @@ -1,7 +1,6 @@ import { DefaultTab } from './components/tab/defaultTab'; import { GroupPanelPartInitParameters, - IActionsRenderer, IContentRenderer, ITabRenderer, } from '../groupview/types'; @@ -12,7 +11,6 @@ import { GroupPanelUpdateEvent } from '../groupview/groupPanel'; export interface IGroupPanelView extends IDisposable { readonly content: IContentRenderer; readonly tab?: ITabRenderer; - readonly actions?: IActionsRenderer; update(event: GroupPanelUpdateEvent): void; layout(width: number, height: number): void; init(params: GroupPanelPartInitParameters): void; @@ -23,37 +21,18 @@ export interface IGroupPanelView extends IDisposable { export class DefaultGroupPanelView implements IGroupPanelView { private readonly _content: IContentRenderer; private readonly _tab: ITabRenderer; - private readonly _actions: IActionsRenderer | undefined; - get content() { + get content(): IContentRenderer { return this._content; } - get tab() { + get tab(): ITabRenderer { return this._tab; } - get actions() { - return this._actions; - } - - constructor(renderers: { - content: IContentRenderer; - tab?: ITabRenderer; - actions?: IActionsRenderer; - }) { + constructor(renderers: { content: IContentRenderer; tab?: ITabRenderer }) { this._content = renderers.content; this._tab = renderers.tab ?? new DefaultTab(); - this._actions = - renderers.actions || - (this.content.actions - ? { - element: this.content.actions, - dispose: () => { - // - }, - } - : undefined); } init(params: GroupPanelPartInitParameters): void { @@ -85,6 +64,5 @@ export class DefaultGroupPanelView implements IGroupPanelView { dispose(): void { this.content.dispose(); this.tab.dispose(); - this.actions?.dispose(); } } diff --git a/packages/dockview/src/dockview/dockviewComponent.ts b/packages/dockview/src/dockview/dockviewComponent.ts index 53dfec761..db9023512 100644 --- a/packages/dockview/src/dockview/dockviewComponent.ts +++ b/packages/dockview/src/dockview/dockviewComponent.ts @@ -36,7 +36,6 @@ import { LayoutMouseEvent, MouseEventKind } from '../groupview/tab'; import { Orientation } from '../splitview/core/splitview'; import { DefaultTab } from './components/tab/defaultTab'; import { - GroupChangeKind2, GroupOptions, GroupPanelViewState, GroupviewDropEvent, @@ -73,6 +72,7 @@ export type DockviewComponentUpdateOptions = Pick< | 'showDndOverlay' | 'watermarkFrameworkComponent' | 'defaultTabComponent' + | 'createGroupControlElement' >; export interface DockviewDropEvent extends GroupviewDropEvent { @@ -716,22 +716,14 @@ export class DockviewComponent view.model.onDidDrop((event) => { this._onDidDrop.fire({ ...event, api: this._api, group: view }); }), - view.model.onDidGroupChange((event) => { - switch (event.kind) { - case GroupChangeKind2.ADD_PANEL: - if (event.panel) { - this._onDidAddPanel.fire(event.panel); - } - break; - case GroupChangeKind2.REMOVE_PANEL: - if (event.panel) { - this._onDidRemovePanel.fire(event.panel); - } - break; - case GroupChangeKind2.PANEL_ACTIVE: - this._onDidActivePanelChange.fire(event.panel); - break; - } + view.model.onDidAddPanel((event) => { + this._onDidAddPanel.fire(event.panel); + }), + view.model.onDidRemovePanel((event) => { + this._onDidRemovePanel.fire(event.panel); + }), + view.model.onDidActivePanelChange((event) => { + this._onDidActivePanelChange.fire(event.panel); }) ); diff --git a/packages/dockview/src/dockview/options.ts b/packages/dockview/src/dockview/options.ts index c36e77b24..ad0652291 100644 --- a/packages/dockview/src/dockview/options.ts +++ b/packages/dockview/src/dockview/options.ts @@ -13,6 +13,7 @@ import { ISplitviewStyles, Orientation } from '../splitview/core/splitview'; import { FrameworkFactory } from '../types'; import { DockviewDropTargets } from '../groupview/dnd'; import { PanelTransfer } from '../dnd/dataTransfer'; +import { IGroupControlRenderer } from '../react/dockview/groupControlsRenderer'; export interface GroupPanelFrameworkComponentFactory { content: FrameworkFactory; @@ -66,6 +67,7 @@ export interface DockviewComponentOptions extends DockviewRenderFunctions { styles?: ISplitviewStyles; defaultTabComponent?: string; showDndOverlay?: (event: DockviewDndOverlayEvent) => boolean; + createGroupControlElement?: (group: GroupPanel) => IGroupControlRenderer; } export interface PanelOptions { diff --git a/packages/dockview/src/groupview/groupview.ts b/packages/dockview/src/groupview/groupview.ts index ce8c474f0..0a506f6e4 100644 --- a/packages/dockview/src/groupview/groupview.ts +++ b/packages/dockview/src/groupview/groupview.ts @@ -14,12 +14,7 @@ import { ITabsContainer, TabsContainer } from './titlebar/tabsContainer'; import { IWatermarkRenderer } from './types'; import { GroupPanel } from './groupviewPanel'; import { DockviewDropTargets } from './dnd'; - -export enum GroupChangeKind2 { - ADD_PANEL = 'ADD_PANEL', - REMOVE_PANEL = 'REMOVE_PANEL', - PANEL_ACTIVE = 'PANEL_ACTIVE', -} +import { IGroupControlRenderer } from '../react/dockview/groupControlsRenderer'; export interface DndService { canDisplayOverlay( @@ -67,15 +62,14 @@ export interface GroupPanelViewState extends CoreGroupOptions { } export interface GroupviewChangeEvent { - readonly kind: GroupChangeKind2; - readonly panel?: IDockviewPanel; + readonly panel: IDockviewPanel; } export interface GroupviewDropEvent { - nativeEvent: DragEvent; - position: Position; + readonly nativeEvent: DragEvent; + readonly position: Position; + readonly index?: number; getData(): PanelTransfer | undefined; - index?: number; } export interface IHeader { @@ -91,7 +85,9 @@ export interface IGroupview extends IDisposable, IGridPanelView { readonly header: IHeader; readonly isContentFocused: boolean; readonly onDidDrop: Event; - readonly onDidGroupChange: Event; + readonly onDidAddPanel: Event; + readonly onDidRemovePanel: Event; + readonly onDidActivePanelChange: Event; readonly onMove: Event; locked: boolean; // state @@ -121,10 +117,11 @@ export class Groupview extends CompositeDisposable implements IGroupview { private readonly tabsContainer: ITabsContainer; private readonly contentContainer: IContentContainer; private readonly dropTarget: Droptarget; - private _activePanel?: IDockviewPanel; + private _activePanel: IDockviewPanel | undefined; private watermark?: IWatermarkRenderer; private _isGroupActive = false; private _locked = false; + private _control: IGroupControlRenderer | undefined; private mostRecentlyUsed: IDockviewPanel[] = []; @@ -140,13 +137,22 @@ export class Groupview extends CompositeDisposable implements IGroupview { private readonly _onMove = new Emitter(); readonly onMove: Event = this._onMove.event; - private readonly _onDidGroupChange = new Emitter(); - readonly onDidGroupChange: Event = - this._onDidGroupChange.event; - private readonly _onDidDrop = new Emitter(); readonly onDidDrop: Event = this._onDidDrop.event; + private readonly _onDidAddPanel = new Emitter(); + readonly onDidAddPanel: Event = + this._onDidAddPanel.event; + + private readonly _onDidRemovePanel = new Emitter(); + readonly onDidRemovePanel: Event = + this._onDidRemovePanel.event; + + private readonly _onDidActivePanelChange = + new Emitter(); + readonly onDidActivePanelChange: Event = + this._onDidActivePanelChange.event; + get element(): HTMLElement { throw new Error('not supported'); } @@ -226,16 +232,10 @@ export class Groupview extends CompositeDisposable implements IGroupview { this.container.classList.add('groupview'); - this.addDisposables( - this._onMove, - this._onDidGroupChange, - this._onDidChange, - this._onDidDrop - ); - this.tabsContainer = new TabsContainer(this.accessor, this.parent, { tabHeight: options.tabHeight, }); + this.contentContainer = new ContentContainer(); this.dropTarget = new Droptarget(this.contentContainer.element, { @@ -268,7 +268,11 @@ export class Groupview extends CompositeDisposable implements IGroupview { this.addDisposables( this._onMove, - this._onDidGroupChange, + this._onDidChange, + this._onDidDrop, + this._onDidAddPanel, + this._onDidRemovePanel, + this._onDidActivePanelChange, this.tabsContainer.onDrop((event) => { this.handleDropEvent(event.event, Position.Center, event.index); }), @@ -284,7 +288,7 @@ export class Groupview extends CompositeDisposable implements IGroupview { ); } - initialize() { + initialize(): void { if (this.options?.panels) { this.options.panels.forEach((panel) => { this.doAddPanel(panel); @@ -299,9 +303,21 @@ export class Groupview extends CompositeDisposable implements IGroupview { // correctly initialized this.setActive(this.isActive, true, true); this.updateContainer(); + + if (this.accessor.options.createGroupControlElement) { + this._control = this.accessor.options.createGroupControlElement( + this.parent + ); + this.addDisposables(this._control); + this._control.init({ + containerApi: new DockviewApi(this.accessor), + api: this.parent.api, + }); + this.tabsContainer.setActionElement(this._control.element); + } } - public indexOf(panel: IDockviewPanel) { + public indexOf(panel: IDockviewPanel): number { return this.tabsContainer.indexOf(panel.id); } @@ -326,7 +342,7 @@ export class Groupview extends CompositeDisposable implements IGroupview { public moveToNext(options?: { panel?: IDockviewPanel; suppressRoll?: boolean; - }) { + }): void { if (!options) { options = {}; } @@ -352,7 +368,7 @@ export class Groupview extends CompositeDisposable implements IGroupview { public moveToPrevious(options?: { panel?: IDockviewPanel; suppressRoll?: boolean; - }) { + }): void { if (!options) { options = {}; } @@ -379,19 +395,19 @@ export class Groupview extends CompositeDisposable implements IGroupview { this.openPanel(this.panels[normalizedIndex]); } - public containsPanel(panel: IDockviewPanel) { + public containsPanel(panel: IDockviewPanel): boolean { return this.panels.includes(panel); } - init(_params: PanelInitParameters) { + init(_params: PanelInitParameters): void { //noop } - update(_params: PanelUpdateEvent) { + update(_params: PanelUpdateEvent): void { //noop } - focus() { + focus(): void { this._activePanel?.focus(); } @@ -403,7 +419,7 @@ export class Groupview extends CompositeDisposable implements IGroupview { skipSetPanelActive?: boolean; skipSetGroupActive?: boolean; } = {} - ) { + ): void { if ( typeof options.index !== 'number' || options.index > this.panels.length @@ -452,7 +468,7 @@ export class Groupview extends CompositeDisposable implements IGroupview { return this._removePanel(panelToRemove); } - public closeAllPanels() { + public closeAllPanels(): void { if (this.panels.length > 0) { // take a copy since we will be edting the array as we iterate through const arrPanelCpy = [...this.panels]; @@ -468,25 +484,23 @@ export class Groupview extends CompositeDisposable implements IGroupview { this.doClose(panel); } - private doClose(panel: IDockviewPanel) { + private doClose(panel: IDockviewPanel): void { this.accessor.removePanel(panel); } - public isPanelActive(panel: IDockviewPanel) { + public isPanelActive(panel: IDockviewPanel): boolean { return this._activePanel === panel; } - updateActions() { - if (this.isActive && this._activePanel?.view?.actions) { - this.tabsContainer.setActionElement( - this._activePanel.view.actions.element - ); - } else { - this.tabsContainer.setActionElement(undefined); - } + updateActions(element: HTMLElement | undefined): void { + this.tabsContainer.setActionElement(element); } - public setActive(isGroupActive: boolean, skipFocus = false, force = false) { + public setActive( + isGroupActive: boolean, + skipFocus = false, + force = false + ): void { if (!force && this.isActive === isGroupActive) { if (!skipFocus) { this._activePanel?.focus(); @@ -514,7 +528,7 @@ export class Groupview extends CompositeDisposable implements IGroupview { } } - public layout(width: number, height: number) { + public layout(width: number, height: number): void { this._width = width; this._height = height; @@ -525,7 +539,7 @@ export class Groupview extends CompositeDisposable implements IGroupview { } } - private _removePanel(panel: IDockviewPanel) { + private _removePanel(panel: IDockviewPanel): IDockviewPanel { const isActivePanel = this._activePanel === panel; this.doRemovePanel(panel); @@ -543,7 +557,7 @@ export class Groupview extends CompositeDisposable implements IGroupview { return panel; } - private doRemovePanel(panel: IDockviewPanel) { + private doRemovePanel(panel: IDockviewPanel): void { const index = this.panels.indexOf(panel); if (this._activePanel === panel) { @@ -560,17 +574,14 @@ export class Groupview extends CompositeDisposable implements IGroupview { ); } - this._onDidGroupChange.fire({ - kind: GroupChangeKind2.REMOVE_PANEL, - panel, - }); + this._onDidRemovePanel.fire({ panel }); } private doAddPanel( panel: IDockviewPanel, index: number = this.panels.length, skipSetActive = false - ) { + ): void { const existingPanel = this._panels.indexOf(panel); const hasExistingPanel = existingPanel > -1; @@ -591,13 +602,10 @@ export class Groupview extends CompositeDisposable implements IGroupview { this.updateMru(panel); this.panels.splice(index, 0, panel); - this._onDidGroupChange.fire({ - kind: GroupChangeKind2.ADD_PANEL, - panel, - }); + this._onDidAddPanel.fire({ panel }); } - private doSetActivePanel(panel: IDockviewPanel | undefined) { + private doSetActivePanel(panel: IDockviewPanel | undefined): void { this._activePanel = panel; if (panel) { @@ -607,14 +615,11 @@ export class Groupview extends CompositeDisposable implements IGroupview { this.updateMru(panel); - this._onDidGroupChange.fire({ - kind: GroupChangeKind2.PANEL_ACTIVE, - panel, - }); + this._onDidActivePanelChange.fire({ panel }); } } - private updateMru(panel: IDockviewPanel) { + private updateMru(panel: IDockviewPanel): void { if (this.mostRecentlyUsed.includes(panel)) { this.mostRecentlyUsed.splice( this.mostRecentlyUsed.indexOf(panel), @@ -624,8 +629,7 @@ export class Groupview extends CompositeDisposable implements IGroupview { this.mostRecentlyUsed = [panel, ...this.mostRecentlyUsed]; } - private updateContainer() { - this.updateActions(); + private updateContainer(): void { toggleClass(this.container, 'empty', this.isEmpty); this.panels.forEach((panel) => @@ -680,7 +684,7 @@ export class Groupview extends CompositeDisposable implements IGroupview { event: DragEvent, position: Position, index?: number - ) { + ): void { const data = getPanelData(); if (data) { @@ -716,7 +720,7 @@ export class Groupview extends CompositeDisposable implements IGroupview { } } - public dispose() { + public dispose(): void { super.dispose(); this.watermark?.dispose(); diff --git a/packages/dockview/src/groupview/types.ts b/packages/dockview/src/groupview/types.ts index 4c50bfc72..e0241c0ba 100644 --- a/packages/dockview/src/groupview/types.ts +++ b/packages/dockview/src/groupview/types.ts @@ -1,4 +1,3 @@ -import { IDisposable } from '../lifecycle'; import { IDockviewComponent } from '../dockview/dockviewComponent'; import { DockviewPanelApi } from '../api/groupPanelApi'; import { PanelInitParameters, IPanel } from '../panel/types'; @@ -42,10 +41,6 @@ export interface ITabRenderer extends IPanel { updateParentGroup(group: GroupPanel, isPanelVisible: boolean): void; } -export interface IActionsRenderer extends IDisposable { - readonly element: HTMLElement; -} - export interface IContentRenderer extends IPanel { readonly element: HTMLElement; readonly actions?: HTMLElement; diff --git a/packages/dockview/src/react/dockview/dockview.tsx b/packages/dockview/src/react/dockview/dockview.tsx index a0d738674..5cda82aa1 100644 --- a/packages/dockview/src/react/dockview/dockview.tsx +++ b/packages/dockview/src/react/dockview/dockview.tsx @@ -12,15 +12,36 @@ import { TabContextMenuEvent, } from '../../dockview/options'; import { DockviewPanelApi } from '../../api/groupPanelApi'; -import { usePortalsLifecycle } from '../react'; +import { ReactPortalStore, usePortalsLifecycle } from '../react'; import { DockviewApi } from '../../api/component.api'; import { IWatermarkPanelProps, ReactWatermarkPart } from './reactWatermarkPart'; import { PanelCollection, PanelParameters } from '../types'; import { watchElementResize } from '../../dom'; import { IContentRenderer, ITabRenderer } from '../../groupview/types'; +import { + IDockviewGroupControlProps, + IGroupControlRenderer, + ReactGroupControlsRendererPart, +} from './groupControlsRenderer'; +import { GroupPanel } from '../../groupview/groupviewPanel'; export const DEFAULT_TAB_IDENTIFIER = '__default__tab__'; +function createGroupControlElement( + component: React.FunctionComponent | undefined, + store: ReactPortalStore +): ((groupPanel: GroupPanel) => IGroupControlRenderer) | undefined { + return component + ? (groupPanel: GroupPanel) => { + return new ReactGroupControlsRendererPart( + component, + store, + groupPanel + ); + } + : undefined; +} + export interface IGroupPanelBaseProps> extends PanelParameters { api: DockviewPanelApi; @@ -50,6 +71,7 @@ export interface IDockviewReactProps { className?: string; disableAutoResizing?: boolean; defaultTabComponent?: React.FunctionComponent; + groupControlComponent?: React.FunctionComponent; } export const DockviewReact = React.forwardRef( @@ -138,6 +160,10 @@ export const DockviewReact = React.forwardRef( ? { separatorBorder: 'transparent' } : undefined, showDndOverlay: props.showDndOverlay, + createGroupControlElement: createGroupControlElement( + props.groupControlComponent, + { addPortal } + ), }); domRef.current?.appendChild(dockview.element); @@ -243,6 +269,18 @@ export const DockviewReact = React.forwardRef( }); }, [props.defaultTabComponent]); + React.useEffect(() => { + if (!dockviewRef.current) { + return; + } + dockviewRef.current.updateOptions({ + createGroupControlElement: createGroupControlElement( + props.groupControlComponent, + { addPortal } + ), + }); + }, [props.groupControlComponent]); + return (
; + + get element(): HTMLElement { + return this._element; + } + + get part(): ReactPart | undefined { + return this._part; + } + + get group(): GroupPanel { + return this._group; + } + + constructor( + private readonly component: React.FunctionComponent, + private readonly reactPortalStore: ReactPortalStore, + private readonly _group: GroupPanel + ) { + this._element = document.createElement('div'); + this._element.className = 'dockview-react-part'; + } + + focus() { + // TODO + } + + public init(parameters: { + containerApi: DockviewApi; + api: GroupviewPanelApi; + }): void { + this.mutableDisposable.value = new CompositeDisposable( + this._group.model.onDidAddPanel(() => { + this.updatePanels(); + }), + this._group.model.onDidRemovePanel(() => { + this.updatePanels(); + }), + this._group.model.onDidActivePanelChange(() => { + this.updateActivePanel(); + }), + parameters.api.onDidActiveChange(() => { + this.updateGroupActive(); + }) + ); + + this._part = new ReactPart( + this.element, + this.reactPortalStore, + this.component, + { + api: parameters.api, + containerApi: parameters.containerApi, + panels: this._group.model.panels, + activePanel: this._group.model.activePanel, + isGroupActive: this._group.api.isActive, + } + ); + } + + public update(event: PanelUpdateEvent) { + this._part?.update(event.params); + } + + public dispose() { + this.mutableDisposable.dispose(); + this._part?.dispose(); + } + + private updatePanels() { + this.update({ params: { panels: this._group.model.panels } }); + } + + private updateActivePanel() { + this.update({ + params: { + activePanel: this._group.model.activePanel, + }, + }); + } + + private updateGroupActive() { + this.update({ + params: { + isGroupActive: this._group.api.isActive, + }, + }); + } +} diff --git a/packages/dockview/src/react/dockview/reactContentPart.ts b/packages/dockview/src/react/dockview/reactContentPart.ts index 07d4036cb..41ea1d954 100644 --- a/packages/dockview/src/react/dockview/reactContentPart.ts +++ b/packages/dockview/src/react/dockview/reactContentPart.ts @@ -2,34 +2,17 @@ import * as React from 'react'; import { IContentRenderer, GroupPanelContentPartInitParameters, - ITabRenderer, } from '../../groupview/types'; import { ReactPart, ReactPortalStore } from '../react'; import { IDockviewPanelProps } from '../dockview/dockview'; import { PanelUpdateEvent } from '../../panel/types'; -import { DockviewPanelApi } from '../../api/groupPanelApi'; -import { DockviewApi } from '../../api/component.api'; import { GroupPanel } from '../../groupview/groupviewPanel'; import { Emitter, Event } from '../../events'; -export interface IGroupPanelActionbarProps { - api: DockviewPanelApi; - containerApi: DockviewApi; -} - -export interface ReactContentPartContext { - api: DockviewPanelApi; - containerApi: DockviewApi; - actionsPortalElement: HTMLElement; - tabPortalElement: ITabRenderer; -} - export class ReactPanelContentPart implements IContentRenderer { private _element: HTMLElement; private part?: ReactPart; // - private _actionsElement: HTMLElement; - private actionsPart?: ReactPart; private _group: GroupPanel | undefined; private readonly _onDidFocus = new Emitter(); @@ -42,10 +25,6 @@ export class ReactPanelContentPart implements IContentRenderer { return this._element; } - get actions(): HTMLElement { - return this._actionsElement; - } - constructor( public readonly id: string, private readonly component: React.FunctionComponent, @@ -53,9 +32,6 @@ export class ReactPanelContentPart implements IContentRenderer { ) { this._element = document.createElement('div'); this._element.className = 'dockview-react-part'; - - this._actionsElement = document.createElement('div'); - this._actionsElement.className = 'dockview-react-part'; } focus() { @@ -63,13 +39,6 @@ export class ReactPanelContentPart implements IContentRenderer { } public init(parameters: GroupPanelContentPartInitParameters): void { - const context: ReactContentPartContext = { - api: parameters.api, - containerApi: parameters.containerApi, - actionsPortalElement: this._actionsElement, - tabPortalElement: parameters.tab, - }; - this.part = new ReactPart( this.element, this.reactPortalStore, @@ -78,8 +47,7 @@ export class ReactPanelContentPart implements IContentRenderer { params: parameters.params, api: parameters.api, containerApi: parameters.containerApi, - }, - context + } ); } @@ -108,6 +76,5 @@ export class ReactPanelContentPart implements IContentRenderer { this._onDidFocus.dispose(); this._onDidBlur.dispose(); this.part?.dispose(); - this.actionsPart?.dispose(); } } diff --git a/packages/dockview/src/react/index.ts b/packages/dockview/src/react/index.ts index 0c30e154d..96c0eef63 100644 --- a/packages/dockview/src/react/index.ts +++ b/packages/dockview/src/react/index.ts @@ -2,8 +2,7 @@ export * from './dockview/dockview'; export * from './dockview/defaultTab'; export * from './splitview/splitview'; export * from './gridview/gridview'; -export * from './dockview/reactContentPart'; -export * from './dockview/reactHeaderPart'; +export { IDockviewGroupControlProps } from './dockview/groupControlsRenderer'; export { IWatermarkPanelProps } from './dockview/reactWatermarkPart'; export * from './paneview/paneview'; export * from './types'; diff --git a/packages/dockview/src/react/react.ts b/packages/dockview/src/react/react.ts index 886499260..73bb1e3ef 100644 --- a/packages/dockview/src/react/react.ts +++ b/packages/dockview/src/react/react.ts @@ -65,6 +65,7 @@ export const ReactPartContext = React.createContext<{}>({}); export class ReactPart

implements IFrameworkPart { + private _initialProps: Record = {}; private componentInstance?: IPanelWrapperRef; private ref?: { portal: React.ReactPortal; disposable: IDisposable }; private disposed = false; @@ -84,7 +85,12 @@ export class ReactPart

throw new Error('invalid operation: resource is already disposed'); } - this.componentInstance?.update(props); + if (!this.componentInstance) { + // if the component is yet to be mounted store the props + this._initialProps = { ...this._initialProps, ...props }; + } else { + this.componentInstance.update(props); + } } private createPortal() { @@ -111,6 +117,11 @@ export class ReactPart

componentProps: this.parameters as unknown as {}, ref: (element: IPanelWrapperRef) => { this.componentInstance = element; + + if (Object.keys(this._initialProps).length > 0) { + this.componentInstance.update(this._initialProps); + this._initialProps = {}; // don't keep a reference to the users object once no longer required + } }, } );