Merge pull request #264 from mathuo/263-left-header-actions

feat: add left header actions
This commit is contained in:
mathuo 2023-06-21 20:09:51 +01:00 committed by GitHub
commit e0f167050c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 335 additions and 102 deletions

View File

@ -1,14 +1,14 @@
import { fireEvent } from '@testing-library/dom'; import { fireEvent } from '@testing-library/dom';
import { Emitter, Event } from '../../../events'; import { Emitter, Event } from '../../../../events';
import { ContentContainer } from '../../../dockview/components/panel/content'; import { ContentContainer } from '../../../../dockview/components/panel/content';
import { import {
GroupPanelContentPartInitParameters, GroupPanelContentPartInitParameters,
IContentRenderer, IContentRenderer,
} from '../../../dockview/types'; } from '../../../../dockview/types';
import { CompositeDisposable } from '../../../lifecycle'; import { CompositeDisposable } from '../../../../lifecycle';
import { PanelUpdateEvent } from '../../../panel/types'; import { PanelUpdateEvent } from '../../../../panel/types';
import { IDockviewPanel } from '../../../dockview/dockviewPanel'; import { IDockviewPanel } from '../../../../dockview/dockviewPanel';
import { IDockviewPanelModel } from '../../../dockview/dockviewPanelModel'; import { IDockviewPanelModel } from '../../../../dockview/dockviewPanelModel';
class TestContentRenderer class TestContentRenderer
extends CompositeDisposable extends CompositeDisposable

View File

@ -1,9 +1,9 @@
import { fireEvent } from '@testing-library/dom'; import { fireEvent } from '@testing-library/dom';
import { LocalSelectionTransfer, PanelTransfer } from '../../dnd/dataTransfer'; import { LocalSelectionTransfer, PanelTransfer } from '../../../dnd/dataTransfer';
import { DockviewComponent } from '../../dockview/dockviewComponent'; import { DockviewComponent } from '../../../dockview/dockviewComponent';
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel'; import { DockviewGroupPanel } from '../../../dockview/dockviewGroupPanel';
import { DockviewGroupPanelModel } from '../../dockview/dockviewGroupPanelModel'; import { DockviewGroupPanelModel } from '../../../dockview/dockviewGroupPanelModel';
import { Tab } from '../../dockview/components/tab/tab'; import { Tab } from '../../../dockview/components/tab/tab';
describe('tab', () => { describe('tab', () => {
test('that empty tab has inactive-tab class', () => { test('that empty tab has inactive-tab class', () => {

View File

@ -1,13 +1,13 @@
import { DockviewComponent } from '../../../dockview/dockviewComponent';
import { TabsContainer } from '../../../dockview/components/titlebar/tabsContainer';
import { fireEvent } from '@testing-library/dom';
import { import {
LocalSelectionTransfer, LocalSelectionTransfer,
PanelTransfer, PanelTransfer,
} from '../../../dnd/dataTransfer'; } from '../../../../dnd/dataTransfer';
import { TestPanel } from '../dockviewGroupPanelModel.spec'; import { TabsContainer } from '../../../../dockview/components/titlebar/tabsContainer';
import { DockviewGroupPanelModel } from '../../../dockview/dockviewGroupPanelModel'; import { DockviewComponent } from '../../../../dockview/dockviewComponent';
import { DockviewGroupPanel } from '../../../dockview/dockviewGroupPanel'; import { DockviewGroupPanel } from '../../../../dockview/dockviewGroupPanel';
import { DockviewGroupPanelModel } from '../../../../dockview/dockviewGroupPanelModel';
import { fireEvent } from '@testing-library/dom';
import { TestPanel } from '../../dockviewGroupPanelModel.spec';
describe('tabsContainer', () => { describe('tabsContainer', () => {
test('that an external event does not render a drop target and calls through to the group mode', () => { test('that an external event does not render a drop target and calls through to the group mode', () => {
@ -331,4 +331,136 @@ describe('tabsContainer', () => {
cut.element.getElementsByClassName('drop-target-dropzone').length cut.element.getElementsByClassName('drop-target-dropzone').length
).toBe(0); ).toBe(0);
}); });
test('left actions', () => {
const accessorMock = jest.fn<DockviewComponent, []>(() => {
return (<Partial<DockviewComponent>>{
options: {},
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
}) as DockviewComponent;
});
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
return (<Partial<DockviewGroupPanel>>{}) as DockviewGroupPanel;
});
const accessor = new accessorMock();
const groupPanel = new groupPanelMock();
const cut = new TabsContainer(accessor, groupPanel);
let query = cut.element.querySelectorAll(
'.tabs-and-actions-container > .left-actions-container'
);
expect(query.length).toBe(1);
expect(query[0].children.length).toBe(0);
// add left action
const left = document.createElement('div');
left.className = 'test-left-actions-element';
cut.setLeftActionsElement(left);
query = cut.element.querySelectorAll(
'.tabs-and-actions-container > .left-actions-container'
);
expect(query.length).toBe(1);
expect(query[0].children.item(0)?.className).toBe(
'test-left-actions-element'
);
expect(query[0].children.length).toBe(1);
// add left action
const left2 = document.createElement('div');
left2.className = 'test-left-actions-element-2';
cut.setLeftActionsElement(left2);
query = cut.element.querySelectorAll(
'.tabs-and-actions-container > .left-actions-container'
);
expect(query.length).toBe(1);
expect(query[0].children.item(0)?.className).toBe(
'test-left-actions-element-2'
);
expect(query[0].children.length).toBe(1);
// remove left action
cut.setLeftActionsElement(undefined);
query = cut.element.querySelectorAll(
'.tabs-and-actions-container > .left-actions-container'
);
expect(query.length).toBe(1);
expect(query[0].children.length).toBe(0);
});
test('right actions', () => {
const accessorMock = jest.fn<DockviewComponent, []>(() => {
return (<Partial<DockviewComponent>>{
options: {},
onDidAddPanel: jest.fn(),
onDidRemovePanel: jest.fn(),
}) as DockviewComponent;
});
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
return (<Partial<DockviewGroupPanel>>{}) as DockviewGroupPanel;
});
const accessor = new accessorMock();
const groupPanel = new groupPanelMock();
const cut = new TabsContainer(accessor, groupPanel);
let query = cut.element.querySelectorAll(
'.tabs-and-actions-container > .right-actions-container'
);
expect(query.length).toBe(1);
expect(query[0].children.length).toBe(0);
// add right action
const right = document.createElement('div');
right.className = 'test-right-actions-element';
cut.setRightActionsElement(right);
query = cut.element.querySelectorAll(
'.tabs-and-actions-container > .right-actions-container'
);
expect(query.length).toBe(1);
expect(query[0].children.item(0)?.className).toBe(
'test-right-actions-element'
);
expect(query[0].children.length).toBe(1);
// add right action
const right2 = document.createElement('div');
right2.className = 'test-right-actions-element-2';
cut.setRightActionsElement(right2);
query = cut.element.querySelectorAll(
'.tabs-and-actions-container > .right-actions-container'
);
expect(query.length).toBe(1);
expect(query[0].children.item(0)?.className).toBe(
'test-right-actions-element-2'
);
expect(query[0].children.length).toBe(1);
// remove right action
cut.setRightActionsElement(undefined);
query = cut.element.querySelectorAll(
'.tabs-and-actions-container > .right-actions-container'
);
expect(query.length).toBe(1);
expect(query[0].children.length).toBe(0);
});
}); });

View File

@ -28,7 +28,8 @@ export interface ITabsContainer extends IDisposable {
isActive: (tab: ITab) => boolean; isActive: (tab: ITab) => boolean;
closePanel: (panel: IDockviewPanel) => void; closePanel: (panel: IDockviewPanel) => void;
openPanel: (panel: IDockviewPanel, index?: number) => void; openPanel: (panel: IDockviewPanel, index?: number) => void;
setActionElement(element: HTMLElement | undefined): void; setRightActionsElement(element: HTMLElement | undefined): void;
setLeftActionsElement(element: HTMLElement | undefined): void;
hidden: boolean; hidden: boolean;
show(): void; show(): void;
hide(): void; hide(): void;
@ -40,12 +41,14 @@ export class TabsContainer
{ {
private readonly _element: HTMLElement; private readonly _element: HTMLElement;
private readonly tabContainer: HTMLElement; private readonly tabContainer: HTMLElement;
private readonly actionContainer: HTMLElement; private readonly rightActionsContainer: HTMLElement;
private readonly leftActionsContainer: HTMLElement;
private readonly voidContainer: VoidContainer; private readonly voidContainer: VoidContainer;
private tabs: IValueDisposable<ITab>[] = []; private tabs: IValueDisposable<ITab>[] = [];
private selectedIndex = -1; private selectedIndex = -1;
private actions: HTMLElement | undefined; private rightActions: HTMLElement | undefined;
private leftActions: HTMLElement | undefined;
private _hidden = false; private _hidden = false;
@ -79,17 +82,31 @@ export class TabsContainer
this._element.style.display = 'none'; this._element.style.display = 'none';
} }
setActionElement(element: HTMLElement | undefined): void { setRightActionsElement(element: HTMLElement | undefined): void {
if (this.actions === element) { if (this.rightActions === element) {
return; return;
} }
if (this.actions) { if (this.rightActions) {
this.actions.remove(); this.rightActions.remove();
this.actions = undefined; this.rightActions = undefined;
} }
if (element) { if (element) {
this.actionContainer.appendChild(element); this.rightActionsContainer.appendChild(element);
this.actions = element; this.rightActions = element;
}
}
setLeftActionsElement(element: HTMLElement | undefined): void {
if (this.leftActions === element) {
return;
}
if (this.leftActions) {
this.leftActions.remove();
this.leftActions = undefined;
}
if (element) {
this.leftActionsContainer.appendChild(element);
this.leftActions = element;
} }
} }
@ -146,8 +163,11 @@ export class TabsContainer
}) })
); );
this.actionContainer = document.createElement('div'); this.rightActionsContainer = document.createElement('div');
this.actionContainer.className = 'action-container'; this.rightActionsContainer.className = 'right-actions-container';
this.leftActionsContainer = document.createElement('div');
this.leftActionsContainer.className = 'left-actions-container';
this.tabContainer = document.createElement('div'); this.tabContainer = document.createElement('div');
this.tabContainer.className = 'tabs-container'; this.tabContainer.className = 'tabs-container';
@ -155,8 +175,9 @@ export class TabsContainer
this.voidContainer = new VoidContainer(this.accessor, this.group); this.voidContainer = new VoidContainer(this.accessor, this.group);
this._element.appendChild(this.tabContainer); this._element.appendChild(this.tabContainer);
this._element.appendChild(this.leftActionsContainer);
this._element.appendChild(this.voidContainer.element); this._element.appendChild(this.voidContainer.element);
this._element.appendChild(this.actionContainer); this._element.appendChild(this.rightActionsContainer);
this.addDisposables( this.addDisposables(
this.voidContainer, this.voidContainer,

View File

@ -71,7 +71,8 @@ export type DockviewComponentUpdateOptions = Pick<
| 'showDndOverlay' | 'showDndOverlay'
| 'watermarkFrameworkComponent' | 'watermarkFrameworkComponent'
| 'defaultTabComponent' | 'defaultTabComponent'
| 'createGroupControlElement' | 'createLeftHeaderActionsElement'
| 'createRightHeaderActionsElement'
>; >;
export interface DockviewDropEvent extends GroupviewDropEvent { export interface DockviewDropEvent extends GroupviewDropEvent {

View File

@ -18,7 +18,7 @@ import {
import { DockviewDropTargets, IWatermarkRenderer } from './types'; import { DockviewDropTargets, IWatermarkRenderer } from './types';
import { DockviewGroupPanel } from './dockviewGroupPanel'; import { DockviewGroupPanel } from './dockviewGroupPanel';
import { IDockviewPanel } from './dockviewPanel'; import { IDockviewPanel } from './dockviewPanel';
import { IGroupControlRenderer } from './options'; import { IHeaderActionsRenderer } from './options';
export interface DndService { export interface DndService {
canDisplayOverlay( canDisplayOverlay(
@ -137,7 +137,8 @@ export class DockviewGroupPanelModel
private watermark?: IWatermarkRenderer; private watermark?: IWatermarkRenderer;
private _isGroupActive = false; private _isGroupActive = false;
private _locked = false; private _locked = false;
private _control: IGroupControlRenderer | undefined; private _rightHeaderActions: IHeaderActionsRenderer | undefined;
private _leftHeaderActions: IHeaderActionsRenderer | undefined;
private mostRecentlyUsed: IDockviewPanel[] = []; private mostRecentlyUsed: IDockviewPanel[] = [];
@ -319,16 +320,34 @@ export class DockviewGroupPanelModel
this.setActive(this.isActive, true, true); this.setActive(this.isActive, true, true);
this.updateContainer(); this.updateContainer();
if (this.accessor.options.createGroupControlElement) { if (this.accessor.options.createRightHeaderActionsElement) {
this._control = this.accessor.options.createGroupControlElement( this._rightHeaderActions =
this.accessor.options.createRightHeaderActionsElement(
this.groupPanel this.groupPanel
); );
this.addDisposables(this._control); this.addDisposables(this._rightHeaderActions);
this._control.init({ this._rightHeaderActions.init({
containerApi: new DockviewApi(this.accessor), containerApi: new DockviewApi(this.accessor),
api: this.groupPanel.api, api: this.groupPanel.api,
}); });
this.tabsContainer.setActionElement(this._control.element); this.tabsContainer.setRightActionsElement(
this._rightHeaderActions.element
);
}
if (this.accessor.options.createLeftHeaderActionsElement) {
this._leftHeaderActions =
this.accessor.options.createLeftHeaderActionsElement(
this.groupPanel
);
this.addDisposables(this._leftHeaderActions);
this._leftHeaderActions.init({
containerApi: new DockviewApi(this.accessor),
api: this.groupPanel.api,
});
this.tabsContainer.setLeftActionsElement(
this._leftHeaderActions.element
);
} }
} }
@ -511,7 +530,7 @@ export class DockviewGroupPanelModel
} }
updateActions(element: HTMLElement | undefined): void { updateActions(element: HTMLElement | undefined): void {
this.tabsContainer.setActionElement(element); this.tabsContainer.setRightActionsElement(element);
} }
public setActive( public setActive(

View File

@ -19,7 +19,7 @@ import { Position } from '../dnd/droptarget';
import { IDockviewPanel } from './dockviewPanel'; import { IDockviewPanel } from './dockviewPanel';
import { FrameworkFactory } from '../panel/componentFactory'; import { FrameworkFactory } from '../panel/componentFactory';
export interface IGroupControlRenderer extends IDisposable { export interface IHeaderActionsRenderer extends IDisposable {
readonly element: HTMLElement; readonly element: HTMLElement;
init(params: { init(params: {
containerApi: DockviewApi; containerApi: DockviewApi;
@ -79,9 +79,12 @@ export interface DockviewComponentOptions extends DockviewRenderFunctions {
styles?: ISplitviewStyles; styles?: ISplitviewStyles;
defaultTabComponent?: string; defaultTabComponent?: string;
showDndOverlay?: (event: DockviewDndOverlayEvent) => boolean; showDndOverlay?: (event: DockviewDndOverlayEvent) => boolean;
createGroupControlElement?: ( createRightHeaderActionsElement?: (
group: DockviewGroupPanel group: DockviewGroupPanel
) => IGroupControlRenderer; ) => IHeaderActionsRenderer;
createLeftHeaderActionsElement?: (
group: DockviewGroupPanel
) => IHeaderActionsRenderer;
singleTabMode?: 'fullwidth' | 'default'; singleTabMode?: 'fullwidth' | 'default';
parentElement?: HTMLElement; parentElement?: HTMLElement;
} }

View File

@ -3,9 +3,9 @@ import {
DockviewGroupPanelApi, DockviewGroupPanelApi,
DockviewGroupPanelModel, DockviewGroupPanelModel,
} from 'dockview-core'; } from 'dockview-core';
import { ReactGroupControlsRendererPart } from '../../dockview/groupControlsRenderer'; import { ReactHeaderActionsRendererPart } from '../../dockview/headerActionsRenderer';
describe('groupControlsRenderer', () => { describe('headerActionsRenderer', () => {
test('#1', () => { test('#1', () => {
const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>( const groupviewMock = jest.fn<Partial<DockviewGroupPanelModel>, []>(
() => { () => {
@ -28,7 +28,7 @@ describe('groupControlsRenderer', () => {
const groupPanel = new groupPanelMock() as DockviewGroupPanel; const groupPanel = new groupPanelMock() as DockviewGroupPanel;
const cut = new ReactGroupControlsRendererPart( const cut = new ReactHeaderActionsRendererPart(
jest.fn(), jest.fn(),
{ {
addPortal: jest.fn(), addPortal: jest.fn(),

View File

@ -4,12 +4,12 @@ import {
DockviewDropEvent, DockviewDropEvent,
DockviewDndOverlayEvent, DockviewDndOverlayEvent,
GroupPanelFrameworkComponentFactory, GroupPanelFrameworkComponentFactory,
IGroupControlRenderer,
DockviewPanelApi, DockviewPanelApi,
DockviewApi, DockviewApi,
IContentRenderer, IContentRenderer,
ITabRenderer, ITabRenderer,
DockviewGroupPanel, DockviewGroupPanel,
IHeaderActionsRenderer,
} from 'dockview-core'; } from 'dockview-core';
import { ReactPanelContentPart } from './reactContentPart'; import { ReactPanelContentPart } from './reactContentPart';
import { ReactPanelHeaderPart } from './reactHeaderPart'; import { ReactPanelHeaderPart } from './reactHeaderPart';
@ -18,17 +18,17 @@ import { ReactPortalStore, usePortalsLifecycle } from '../react';
import { IWatermarkPanelProps, ReactWatermarkPart } from './reactWatermarkPart'; import { IWatermarkPanelProps, ReactWatermarkPart } from './reactWatermarkPart';
import { PanelCollection, PanelParameters } from '../types'; import { PanelCollection, PanelParameters } from '../types';
import { import {
IDockviewGroupControlProps, IDockviewHeaderActionsProps,
ReactGroupControlsRendererPart, ReactHeaderActionsRendererPart,
} from './groupControlsRenderer'; } from './headerActionsRenderer';
function createGroupControlElement( function createGroupControlElement(
component: React.FunctionComponent<IDockviewGroupControlProps> | undefined, component: React.FunctionComponent<IDockviewHeaderActionsProps> | undefined,
store: ReactPortalStore store: ReactPortalStore
): ((groupPanel: DockviewGroupPanel) => IGroupControlRenderer) | undefined { ): ((groupPanel: DockviewGroupPanel) => IHeaderActionsRenderer) | undefined {
return component return component
? (groupPanel: DockviewGroupPanel) => { ? (groupPanel: DockviewGroupPanel) => {
return new ReactGroupControlsRendererPart( return new ReactHeaderActionsRendererPart(
component, component,
store, store,
groupPanel groupPanel
@ -65,7 +65,8 @@ export interface IDockviewReactProps {
className?: string; className?: string;
disableAutoResizing?: boolean; disableAutoResizing?: boolean;
defaultTabComponent?: React.FunctionComponent<IDockviewPanelHeaderProps>; defaultTabComponent?: React.FunctionComponent<IDockviewPanelHeaderProps>;
groupControlComponent?: React.FunctionComponent<IDockviewGroupControlProps>; rightHeaderActionsComponent?: React.FunctionComponent<IDockviewHeaderActionsProps>;
leftHeaderActionsComponent?: React.FunctionComponent<IDockviewHeaderActionsProps>;
singleTabMode?: 'fullwidth' | 'default'; singleTabMode?: 'fullwidth' | 'default';
} }
@ -150,10 +151,15 @@ export const DockviewReact = React.forwardRef(
? { separatorBorder: 'transparent' } ? { separatorBorder: 'transparent' }
: undefined, : undefined,
showDndOverlay: props.showDndOverlay, showDndOverlay: props.showDndOverlay,
createGroupControlElement: createGroupControlElement( createLeftHeaderActionsElement: createGroupControlElement(
props.groupControlComponent, props.leftHeaderActionsComponent,
{ addPortal } { addPortal }
), ),
createRightHeaderActionsElement: createGroupControlElement(
props.rightHeaderActionsComponent,
{ addPortal }
),
singleTabMode: props.singleTabMode, singleTabMode: props.singleTabMode,
}); });
@ -250,12 +256,24 @@ export const DockviewReact = React.forwardRef(
return; return;
} }
dockviewRef.current.updateOptions({ dockviewRef.current.updateOptions({
createGroupControlElement: createGroupControlElement( createRightHeaderActionsElement: createGroupControlElement(
props.groupControlComponent, props.rightHeaderActionsComponent,
{ addPortal } { addPortal }
), ),
}); });
}, [props.groupControlComponent]); }, [props.rightHeaderActionsComponent]);
React.useEffect(() => {
if (!dockviewRef.current) {
return;
}
dockviewRef.current.updateOptions({
createLeftHeaderActionsElement: createGroupControlElement(
props.leftHeaderActionsComponent,
{ addPortal }
),
});
}, [props.leftHeaderActionsComponent]);
return ( return (
<div <div

View File

@ -10,24 +10,25 @@ import {
PanelUpdateEvent, PanelUpdateEvent,
} from 'dockview-core'; } from 'dockview-core';
export interface IDockviewGroupControlProps { export interface IDockviewHeaderActionsProps {
api: DockviewGroupPanelApi; api: DockviewGroupPanelApi;
containerApi: DockviewApi; containerApi: DockviewApi;
panels: IDockviewPanel[]; panels: IDockviewPanel[];
activePanel: IDockviewPanel | undefined; activePanel: IDockviewPanel | undefined;
isGroupActive: boolean; isGroupActive: boolean;
group: DockviewGroupPanel;
} }
export class ReactGroupControlsRendererPart { export class ReactHeaderActionsRendererPart {
private mutableDisposable = new DockviewMutableDisposable(); private mutableDisposable = new DockviewMutableDisposable();
private _element: HTMLElement; private _element: HTMLElement;
private _part?: ReactPart<IDockviewGroupControlProps>; private _part?: ReactPart<IDockviewHeaderActionsProps>;
get element(): HTMLElement { get element(): HTMLElement {
return this._element; return this._element;
} }
get part(): ReactPart<IDockviewGroupControlProps> | undefined { get part(): ReactPart<IDockviewHeaderActionsProps> | undefined {
return this._part; return this._part;
} }
@ -36,7 +37,7 @@ export class ReactGroupControlsRendererPart {
} }
constructor( constructor(
private readonly component: React.FunctionComponent<IDockviewGroupControlProps>, private readonly component: React.FunctionComponent<IDockviewHeaderActionsProps>,
private readonly reactPortalStore: ReactPortalStore, private readonly reactPortalStore: ReactPortalStore,
private readonly _group: DockviewGroupPanel private readonly _group: DockviewGroupPanel
) { ) {
@ -77,6 +78,7 @@ export class ReactGroupControlsRendererPart {
panels: this._group.model.panels, panels: this._group.model.panels,
activePanel: this._group.model.activePanel, activePanel: this._group.model.activePanel,
isGroupActive: this._group.api.isActive, isGroupActive: this._group.api.isActive,
group: this._group,
} }
); );
} }

View File

@ -4,7 +4,7 @@ export * from './dockview/dockview';
export * from './dockview/defaultTab'; export * from './dockview/defaultTab';
export * from './splitview/splitview'; export * from './splitview/splitview';
export * from './gridview/gridview'; export * from './gridview/gridview';
export { IDockviewGroupControlProps } from './dockview/groupControlsRenderer'; export { IDockviewHeaderActionsProps } from './dockview/headerActionsRenderer';
export { IWatermarkPanelProps } from './dockview/reactWatermarkPart'; export { IWatermarkPanelProps } from './dockview/reactWatermarkPart';
export * from './paneview/paneview'; export * from './paneview/paneview';
export * from './types'; export * from './types';

View File

@ -18,7 +18,7 @@ import DockviewConstraints from '@site/sandboxes/constraints-dockview/src/app';
import DndDockview from '@site/sandboxes/dnd-dockview/src/app'; import DndDockview from '@site/sandboxes/dnd-dockview/src/app';
import NestedDockview from '@site/sandboxes/nested-dockview/src/app'; import NestedDockview from '@site/sandboxes/nested-dockview/src/app';
import EventsDockview from '@site/sandboxes/events-dockview/src/app'; import EventsDockview from '@site/sandboxes/events-dockview/src/app';
import DockviewGroupControl from '@site/sandboxes/groupcontrol-dockview/src/app'; import DockviewGroupControl from '@site/sandboxes/headeractions-dockview/src/app';
import CustomHeadersDockview from '@site/sandboxes/customheader-dockview/src/app'; import CustomHeadersDockview from '@site/sandboxes/customheader-dockview/src/app';
import DockviewNative from '@site/sandboxes/fullwidthtab-dockview/src/app'; import DockviewNative from '@site/sandboxes/fullwidthtab-dockview/src/app';
import DockviewNative2 from '@site/sandboxes/nativeapp-dockview/src/app'; import DockviewNative2 from '@site/sandboxes/nativeapp-dockview/src/app';
@ -60,7 +60,7 @@ import { DockviewReact } from 'dockview';
``` ```
| Property | Type | Optional | Default | Description | | Property | Type | Optional | Default | Description |
| --------------------- | ------------------------------------ | -------- | --------- | ------------------------------------------------------------ | | --------------------------- | ------------------------------------ | -------- | --------- | ------------------------------------------------------------ |
| onReady | (event: SplitviewReadyEvent) => void | No | | | | onReady | (event: SplitviewReadyEvent) => void | No | | |
| components | object | No | | | | components | object | No | | |
| tabComponents | object | Yes | | | | tabComponents | object | Yes | | |
@ -71,7 +71,8 @@ import { DockviewReact } from 'dockview';
| onDidDrop | Event | Yes | false | | | onDidDrop | Event | Yes | false | |
| showDndOverlay | Event | Yes | false | | | showDndOverlay | Event | Yes | false | |
| defaultTabComponent | object | Yes | | | | defaultTabComponent | object | Yes | | |
| groupControlComponent | object | Yes | | | | leftHeaderActionsComponent | object | Yes | | |
| rightHeaderActionsComponent | object | Yes | | |
| singleTabMode | 'fullwidth' \| 'default' | Yes | 'default' | | | singleTabMode | 'fullwidth' \| 'default' | Yes | 'default' | |
## Dockview API ## Dockview API
@ -683,22 +684,22 @@ panel.group.locked = true;
### Group Controls Panel ### Group Controls Panel
`DockviewReact` accepts a prop `groupControlComponent` which expects a React component whos props are `IDockviewGroupControlProps`. `DockviewReact` accepts `leftHeaderActionsComponent` and `rightHeaderActionsComponent` which expect a React component with props `IDockviewHeaderActionsProps`.
This control will be rendered inside the header bar on the right hand side for each group of tabs. These controls are rendered of the left and right side of the space to the right of the tabs in the header bar.
```tsx ```tsx
const Component: React.FunctionComponent<IDockviewGroupControlProps> = () => { const Component: React.FunctionComponent<IDockviewHeaderActionsProps> = () => {
return <div>{'...'}</div>; return <div>{'...'}</div>;
}; };
return <DockviewReact {...props} groupControlComponent={Component} />; return <DockviewReact {...props} leftHeaderActionsComponent={Component} rightHeaderActionsComponent={...} />;
``` ```
As a simple example the below uses the `groupControlComponent` to render a small control that indicates whether the group As a simple example the below uses the `groupControlComponent` to render a small control that indicates whether the group
is active and which panel is active in that group. is active and which panel is active in that group.
```tsx ```tsx
const GroupControlComponent = (props: IDockviewGroupControlProps) => { const RightHeaderActionsComponent = (props: IDockviewHeaderActionsProps) => {
const isGroupActive = props.isGroupActive; const isGroupActive = props.isGroupActive;
const activePanel = props.activePanel; const activePanel = props.activePanel;

View File

@ -4,7 +4,7 @@ import {
DockviewReadyEvent, DockviewReadyEvent,
IDockviewPanelHeaderProps, IDockviewPanelHeaderProps,
IDockviewPanelProps, IDockviewPanelProps,
IDockviewGroupControlProps, IDockviewHeaderActionsProps,
} from 'dockview'; } from 'dockview';
import * as React from 'react'; import * as React from 'react';
import * as ReactDOM from 'react-dom'; import * as ReactDOM from 'react-dom';
@ -134,7 +134,7 @@ const groupControlsComponents = {
}, },
}; };
const GroupControls = (props: IDockviewGroupControlProps) => { const RightControls = (props: IDockviewHeaderActionsProps) => {
const Component = React.useMemo(() => { const Component = React.useMemo(() => {
if (!props.isGroupActive || !props.activePanel) { if (!props.isGroupActive || !props.activePanel) {
return null; return null;
@ -161,6 +161,36 @@ const GroupControls = (props: IDockviewGroupControlProps) => {
); );
}; };
let counter = 0;
const LeftControls = (props: IDockviewHeaderActionsProps) => {
const onClick = () => {
props.containerApi.addPanel({
id: `id_${Date.now().toString()}`,
component: 'default',
title: `Tab ${counter++}`,
position: {
referenceGroup: props.group,
},
});
};
return (
<div
className="group-control"
style={{
display: 'flex',
alignItems: 'center',
padding: '0px 8px',
height: '100%',
color: 'var(--dv-activegroup-visiblepanel-tab-color)',
}}
>
<Icon onClick={onClick} icon="add" />
</div>
);
};
const DockviewDemo = () => { const DockviewDemo = () => {
const onReady = (event: DockviewReadyEvent) => { const onReady = (event: DockviewReadyEvent) => {
event.api.addPanel({ event.api.addPanel({
@ -196,8 +226,6 @@ const DockviewDemo = () => {
title: 'Panel 6', title: 'Panel 6',
position: { referencePanel: 'panel_4', direction: 'below' }, position: { referencePanel: 'panel_4', direction: 'below' },
}); });
panel6.group.locked = true;
panel6.group.header.hidden = true;
event.api.addPanel({ event.api.addPanel({
id: 'panel_7', id: 'panel_7',
component: 'default', component: 'default',
@ -211,8 +239,6 @@ const DockviewDemo = () => {
position: { referencePanel: 'panel_7', direction: 'within' }, position: { referencePanel: 'panel_7', direction: 'within' },
}); });
event.api.addGroup();
event.api.getPanel('panel_1')!.api.setActive(); event.api.getPanel('panel_1')!.api.setActive();
}; };
@ -220,7 +246,8 @@ const DockviewDemo = () => {
<DockviewReact <DockviewReact
components={components} components={components}
defaultTabComponent={headerComponents.default} defaultTabComponent={headerComponents.default}
groupControlComponent={GroupControls} rightHeaderActionsComponent={RightControls}
leftHeaderActionsComponent={LeftControls}
onReady={onReady} onReady={onReady}
className="dockview-theme-abyss" className="dockview-theme-abyss"
/> />

View File

@ -1,5 +1,5 @@
{ {
"name": "groupcontrol-dockview", "name": "headeractions-dockview",
"description": "", "description": "",
"keywords": [ "keywords": [
"dockview" "dockview"

View File

@ -1,7 +1,7 @@
import { import {
DockviewReact, DockviewReact,
DockviewReadyEvent, DockviewReadyEvent,
IDockviewGroupControlProps, IDockviewHeaderActionsProps,
IDockviewPanelProps, IDockviewPanelProps,
} from 'dockview'; } from 'dockview';
import * as React from 'react'; import * as React from 'react';
@ -26,9 +26,8 @@ const components = {
}, },
}; };
const GroupControlComponent = (props: IDockviewGroupControlProps) => { const RightHeaderActions = (props: IDockviewHeaderActionsProps) => {
const isGroupActive = props.isGroupActive; const isGroupActive = props.isGroupActive;
const activePanel = props.activePanel;
return ( return (
<div className="dockview-groupcontrol-demo"> <div className="dockview-groupcontrol-demo">
@ -40,6 +39,15 @@ const GroupControlComponent = (props: IDockviewGroupControlProps) => {
> >
{isGroupActive ? 'Group Active' : 'Group Inactive'} {isGroupActive ? 'Group Active' : 'Group Inactive'}
</span> </span>
</div>
);
};
const LeftHeaderActions = (props: IDockviewHeaderActionsProps) => {
const activePanel = props.activePanel;
return (
<div className="dockview-groupcontrol-demo">
<span className="dockview-groupcontrol-demo-active-panel">{`activePanel: ${ <span className="dockview-groupcontrol-demo-active-panel">{`activePanel: ${
activePanel?.id || 'null' activePanel?.id || 'null'
}`}</span> }`}</span>
@ -87,7 +95,8 @@ const DockviewGroupControl = () => {
<DockviewReact <DockviewReact
onReady={onReady} onReady={onReady}
components={components} components={components}
groupControlComponent={GroupControlComponent} leftHeaderActionsComponent={LeftHeaderActions}
rightHeaderActionsComponent={RightHeaderActions}
className="dockview-theme-abyss" className="dockview-theme-abyss"
/> />
); );

View File

@ -18,7 +18,7 @@ import DockviewConstraints from '@site/sandboxes/constraints-dockview/src/app';
import DndDockview from '@site/sandboxes/dnd-dockview/src/app'; import DndDockview from '@site/sandboxes/dnd-dockview/src/app';
import NestedDockview from '@site/sandboxes/nested-dockview/src/app'; import NestedDockview from '@site/sandboxes/nested-dockview/src/app';
import EventsDockview from '@site/sandboxes/events-dockview/src/app'; import EventsDockview from '@site/sandboxes/events-dockview/src/app';
import DockviewGroupControl from '@site/sandboxes/groupcontrol-dockview/src/app'; import DockviewGroupControl from '@site/sandboxes/headeractions-dockview/src/app';
import CustomHeadersDockview from '@site/sandboxes/customheader-dockview/src/app'; import CustomHeadersDockview from '@site/sandboxes/customheader-dockview/src/app';
import DockviewNative from '@site/sandboxes/fullwidthtab-dockview/src/app'; import DockviewNative from '@site/sandboxes/fullwidthtab-dockview/src/app';
import DockviewNative2 from '@site/sandboxes/nativeapp-dockview/src/app'; import DockviewNative2 from '@site/sandboxes/nativeapp-dockview/src/app';