mirror of
https://github.com/mathuo/dockview
synced 2025-03-11 16:32:06 +00:00
Merge pull request #340 from mathuo/338-options-before-tabs-start
feat: pre-tab actions
This commit is contained in:
commit
2337373a6f
@ -638,4 +638,214 @@ describe('tabsContainer', () => {
|
||||
expect(preventDefaultSpy).toBeCalledTimes(1);
|
||||
expect(accessor.addFloatingGroup).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
test('pre header actions', () => {
|
||||
const accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
return (<Partial<DockviewComponent>>{
|
||||
options: {},
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
element: document.createElement('div'),
|
||||
addFloatingGroup: jest.fn(),
|
||||
getGroupPanel: jest.fn(),
|
||||
}) as DockviewComponent;
|
||||
});
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return (<Partial<DockviewGroupPanel>>{
|
||||
api: { isFloating: true } as any,
|
||||
model: {} as any,
|
||||
}) as DockviewGroupPanel;
|
||||
});
|
||||
|
||||
const accessor = new accessorMock();
|
||||
const groupPanel = new groupPanelMock();
|
||||
|
||||
const cut = new TabsContainer(accessor, groupPanel);
|
||||
|
||||
const panelMock = jest.fn<IDockviewPanel, [string]>((id: string) => {
|
||||
const partial: Partial<IDockviewPanel> = {
|
||||
id,
|
||||
|
||||
view: {
|
||||
tab: {
|
||||
element: document.createElement('div'),
|
||||
} as any,
|
||||
content: {
|
||||
element: document.createElement('div'),
|
||||
} as any,
|
||||
} as any,
|
||||
};
|
||||
return partial as IDockviewPanel;
|
||||
});
|
||||
|
||||
const panel = new panelMock('test_id');
|
||||
cut.openPanel(panel);
|
||||
|
||||
let result = cut.element.querySelector('.pre-actions-container');
|
||||
expect(result).toBeTruthy();
|
||||
expect(result!.childNodes.length).toBe(0);
|
||||
|
||||
const actions = document.createElement('div');
|
||||
cut.setPrefixActionsElement(actions);
|
||||
|
||||
result = cut.element.querySelector('.pre-actions-container');
|
||||
expect(result).toBeTruthy();
|
||||
expect(result!.childNodes.length).toBe(1);
|
||||
expect(result!.childNodes.item(0)).toBe(actions);
|
||||
|
||||
const updatedActions = document.createElement('div');
|
||||
cut.setPrefixActionsElement(updatedActions);
|
||||
|
||||
result = cut.element.querySelector('.pre-actions-container');
|
||||
expect(result).toBeTruthy();
|
||||
expect(result!.childNodes.length).toBe(1);
|
||||
expect(result!.childNodes.item(0)).toBe(updatedActions);
|
||||
|
||||
cut.setPrefixActionsElement(undefined);
|
||||
|
||||
result = cut.element.querySelector('.pre-actions-container');
|
||||
expect(result).toBeTruthy();
|
||||
expect(result!.childNodes.length).toBe(0);
|
||||
});
|
||||
|
||||
test('left header actions', () => {
|
||||
const accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
return (<Partial<DockviewComponent>>{
|
||||
options: {},
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
element: document.createElement('div'),
|
||||
addFloatingGroup: jest.fn(),
|
||||
getGroupPanel: jest.fn(),
|
||||
}) as DockviewComponent;
|
||||
});
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return (<Partial<DockviewGroupPanel>>{
|
||||
api: { isFloating: true } as any,
|
||||
model: {} as any,
|
||||
}) as DockviewGroupPanel;
|
||||
});
|
||||
|
||||
const accessor = new accessorMock();
|
||||
const groupPanel = new groupPanelMock();
|
||||
|
||||
const cut = new TabsContainer(accessor, groupPanel);
|
||||
|
||||
const panelMock = jest.fn<IDockviewPanel, [string]>((id: string) => {
|
||||
const partial: Partial<IDockviewPanel> = {
|
||||
id,
|
||||
|
||||
view: {
|
||||
tab: {
|
||||
element: document.createElement('div'),
|
||||
} as any,
|
||||
content: {
|
||||
element: document.createElement('div'),
|
||||
} as any,
|
||||
} as any,
|
||||
};
|
||||
return partial as IDockviewPanel;
|
||||
});
|
||||
|
||||
const panel = new panelMock('test_id');
|
||||
cut.openPanel(panel);
|
||||
|
||||
let result = cut.element.querySelector('.left-actions-container');
|
||||
expect(result).toBeTruthy();
|
||||
expect(result!.childNodes.length).toBe(0);
|
||||
|
||||
const actions = document.createElement('div');
|
||||
cut.setLeftActionsElement(actions);
|
||||
|
||||
result = cut.element.querySelector('.left-actions-container');
|
||||
expect(result).toBeTruthy();
|
||||
expect(result!.childNodes.length).toBe(1);
|
||||
expect(result!.childNodes.item(0)).toBe(actions);
|
||||
|
||||
const updatedActions = document.createElement('div');
|
||||
cut.setLeftActionsElement(updatedActions);
|
||||
|
||||
result = cut.element.querySelector('.left-actions-container');
|
||||
expect(result).toBeTruthy();
|
||||
expect(result!.childNodes.length).toBe(1);
|
||||
expect(result!.childNodes.item(0)).toBe(updatedActions);
|
||||
|
||||
cut.setLeftActionsElement(undefined);
|
||||
|
||||
result = cut.element.querySelector('.left-actions-container');
|
||||
expect(result).toBeTruthy();
|
||||
expect(result!.childNodes.length).toBe(0);
|
||||
});
|
||||
|
||||
test('right header actions', () => {
|
||||
const accessorMock = jest.fn<DockviewComponent, []>(() => {
|
||||
return (<Partial<DockviewComponent>>{
|
||||
options: {},
|
||||
onDidAddPanel: jest.fn(),
|
||||
onDidRemovePanel: jest.fn(),
|
||||
element: document.createElement('div'),
|
||||
addFloatingGroup: jest.fn(),
|
||||
getGroupPanel: jest.fn(),
|
||||
}) as DockviewComponent;
|
||||
});
|
||||
|
||||
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
|
||||
return (<Partial<DockviewGroupPanel>>{
|
||||
api: { isFloating: true } as any,
|
||||
model: {} as any,
|
||||
}) as DockviewGroupPanel;
|
||||
});
|
||||
|
||||
const accessor = new accessorMock();
|
||||
const groupPanel = new groupPanelMock();
|
||||
|
||||
const cut = new TabsContainer(accessor, groupPanel);
|
||||
|
||||
const panelMock = jest.fn<IDockviewPanel, [string]>((id: string) => {
|
||||
const partial: Partial<IDockviewPanel> = {
|
||||
id,
|
||||
|
||||
view: {
|
||||
tab: {
|
||||
element: document.createElement('div'),
|
||||
} as any,
|
||||
content: {
|
||||
element: document.createElement('div'),
|
||||
} as any,
|
||||
} as any,
|
||||
};
|
||||
return partial as IDockviewPanel;
|
||||
});
|
||||
|
||||
const panel = new panelMock('test_id');
|
||||
cut.openPanel(panel);
|
||||
|
||||
let result = cut.element.querySelector('.right-actions-container');
|
||||
expect(result).toBeTruthy();
|
||||
expect(result!.childNodes.length).toBe(0);
|
||||
|
||||
const actions = document.createElement('div');
|
||||
cut.setRightActionsElement(actions);
|
||||
|
||||
result = cut.element.querySelector('.right-actions-container');
|
||||
expect(result).toBeTruthy();
|
||||
expect(result!.childNodes.length).toBe(1);
|
||||
expect(result!.childNodes.item(0)).toBe(actions);
|
||||
|
||||
const updatedActions = document.createElement('div');
|
||||
cut.setRightActionsElement(updatedActions);
|
||||
|
||||
result = cut.element.querySelector('.right-actions-container');
|
||||
expect(result).toBeTruthy();
|
||||
expect(result!.childNodes.length).toBe(1);
|
||||
expect(result!.childNodes.item(0)).toBe(updatedActions);
|
||||
|
||||
cut.setRightActionsElement(undefined);
|
||||
|
||||
result = cut.element.querySelector('.right-actions-container');
|
||||
expect(result).toBeTruthy();
|
||||
expect(result!.childNodes.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
@ -43,6 +43,7 @@ export interface ITabsContainer extends IDisposable {
|
||||
openPanel: (panel: IDockviewPanel, index?: number) => void;
|
||||
setRightActionsElement(element: HTMLElement | undefined): void;
|
||||
setLeftActionsElement(element: HTMLElement | undefined): void;
|
||||
setPrefixActionsElement(element: HTMLElement | undefined): void;
|
||||
show(): void;
|
||||
hide(): void;
|
||||
}
|
||||
@ -55,12 +56,14 @@ export class TabsContainer
|
||||
private readonly tabContainer: HTMLElement;
|
||||
private readonly rightActionsContainer: HTMLElement;
|
||||
private readonly leftActionsContainer: HTMLElement;
|
||||
private readonly preActionsContainer: HTMLElement;
|
||||
private readonly voidContainer: VoidContainer;
|
||||
|
||||
private tabs: IValueDisposable<ITab>[] = [];
|
||||
private selectedIndex = -1;
|
||||
private rightActions: HTMLElement | undefined;
|
||||
private leftActions: HTMLElement | undefined;
|
||||
private preActions: HTMLElement | undefined;
|
||||
|
||||
private _hidden = false;
|
||||
|
||||
@ -129,6 +132,20 @@ export class TabsContainer
|
||||
}
|
||||
}
|
||||
|
||||
setPrefixActionsElement(element: HTMLElement | undefined): void {
|
||||
if (this.preActions === element) {
|
||||
return;
|
||||
}
|
||||
if (this.preActions) {
|
||||
this.preActions.remove();
|
||||
this.preActions = undefined;
|
||||
}
|
||||
if (element) {
|
||||
this.preActionsContainer.appendChild(element);
|
||||
this.preActions = element;
|
||||
}
|
||||
}
|
||||
|
||||
get element(): HTMLElement {
|
||||
return this._element;
|
||||
}
|
||||
@ -192,11 +209,15 @@ export class TabsContainer
|
||||
this.leftActionsContainer = document.createElement('div');
|
||||
this.leftActionsContainer.className = 'left-actions-container';
|
||||
|
||||
this.preActionsContainer = document.createElement('div');
|
||||
this.preActionsContainer.className = 'pre-actions-container';
|
||||
|
||||
this.tabContainer = document.createElement('div');
|
||||
this.tabContainer.className = 'tabs-container';
|
||||
|
||||
this.voidContainer = new VoidContainer(this.accessor, this.group);
|
||||
|
||||
this._element.appendChild(this.preActionsContainer);
|
||||
this._element.appendChild(this.tabContainer);
|
||||
this._element.appendChild(this.leftActionsContainer);
|
||||
this._element.appendChild(this.voidContainer.element);
|
||||
|
@ -92,6 +92,7 @@ export type DockviewComponentUpdateOptions = Pick<
|
||||
| 'defaultTabComponent'
|
||||
| 'createLeftHeaderActionsElement'
|
||||
| 'createRightHeaderActionsElement'
|
||||
| 'createPrefixHeaderActionsElement'
|
||||
| 'disableFloatingGroups'
|
||||
| 'floatingGroupBounds'
|
||||
>;
|
||||
|
@ -144,6 +144,7 @@ export class DockviewGroupPanelModel
|
||||
private _isFloating = false;
|
||||
private _rightHeaderActions: IHeaderActionsRenderer | undefined;
|
||||
private _leftHeaderActions: IHeaderActionsRenderer | undefined;
|
||||
private _prefixHeaderActions: IHeaderActionsRenderer | undefined;
|
||||
|
||||
private mostRecentlyUsed: IDockviewPanel[] = [];
|
||||
|
||||
@ -398,6 +399,21 @@ export class DockviewGroupPanelModel
|
||||
this._leftHeaderActions.element
|
||||
);
|
||||
}
|
||||
|
||||
if (this.accessor.options.createPrefixHeaderActionsElement) {
|
||||
this._prefixHeaderActions =
|
||||
this.accessor.options.createPrefixHeaderActionsElement(
|
||||
this.groupPanel
|
||||
);
|
||||
this.addDisposables(this._prefixHeaderActions);
|
||||
this._prefixHeaderActions.init({
|
||||
containerApi: new DockviewApi(this.accessor),
|
||||
api: this.groupPanel.api,
|
||||
});
|
||||
this.tabsContainer.setPrefixActionsElement(
|
||||
this._prefixHeaderActions.element
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public indexOf(panel: IDockviewPanel): number {
|
||||
|
@ -84,6 +84,9 @@ export interface DockviewComponentOptions extends DockviewRenderFunctions {
|
||||
createLeftHeaderActionsElement?: (
|
||||
group: DockviewGroupPanel
|
||||
) => IHeaderActionsRenderer;
|
||||
createPrefixHeaderActionsElement?: (
|
||||
group: DockviewGroupPanel
|
||||
) => IHeaderActionsRenderer;
|
||||
singleTabMode?: 'fullwidth' | 'default';
|
||||
parentElement?: HTMLElement;
|
||||
disableFloatingGroups?: boolean;
|
||||
|
@ -67,6 +67,7 @@ export interface IDockviewReactProps {
|
||||
defaultTabComponent?: React.FunctionComponent<IDockviewPanelHeaderProps>;
|
||||
rightHeaderActionsComponent?: React.FunctionComponent<IDockviewHeaderActionsProps>;
|
||||
leftHeaderActionsComponent?: React.FunctionComponent<IDockviewHeaderActionsProps>;
|
||||
prefixHeaderActionsComponent?: React.FunctionComponent<IDockviewHeaderActionsProps>;
|
||||
singleTabMode?: 'fullwidth' | 'default';
|
||||
disableFloatingGroups?: boolean;
|
||||
floatingGroupBounds?:
|
||||
@ -166,6 +167,10 @@ export const DockviewReact = React.forwardRef(
|
||||
props.rightHeaderActionsComponent,
|
||||
{ addPortal }
|
||||
),
|
||||
createPrefixHeaderActionsElement: createGroupControlElement(
|
||||
props.prefixHeaderActionsComponent,
|
||||
{ addPortal }
|
||||
),
|
||||
singleTabMode: props.singleTabMode,
|
||||
disableFloatingGroups: props.disableFloatingGroups,
|
||||
floatingGroupBounds: props.floatingGroupBounds,
|
||||
@ -301,6 +306,18 @@ export const DockviewReact = React.forwardRef(
|
||||
});
|
||||
}, [props.leftHeaderActionsComponent]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!dockviewRef.current) {
|
||||
return;
|
||||
}
|
||||
dockviewRef.current.updateOptions({
|
||||
createPrefixHeaderActionsElement: createGroupControlElement(
|
||||
props.prefixHeaderActionsComponent,
|
||||
{ addPortal }
|
||||
),
|
||||
});
|
||||
}, [props.prefixHeaderActionsComponent]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={props.className}
|
||||
|
@ -59,7 +59,7 @@ import { DockviewReact } from 'dockview';
|
||||
```
|
||||
|
||||
| Property | Type | Optional | Default | Description |
|
||||
| --------------------------- | ------------------------------------ | -------- | --------- | ----------- |
|
||||
| ---------------------------- | ------------------------------------ | -------- | --------- | ----------- |
|
||||
| onReady | (event: SplitviewReadyEvent) => void | No | | |
|
||||
| components | object | No | | |
|
||||
| tabComponents | object | Yes | | |
|
||||
@ -72,6 +72,7 @@ import { DockviewReact } from 'dockview';
|
||||
| defaultTabComponent | object | Yes | | |
|
||||
| leftHeaderActionsComponent | object | Yes | | |
|
||||
| rightHeaderActionsComponent | object | Yes | | |
|
||||
| prefixHeaderActionsComponent | object | Yes | | |
|
||||
| singleTabMode | 'fullwidth' \| 'default' | Yes | 'default' | |
|
||||
|
||||
## Dockview API
|
||||
@ -804,8 +805,8 @@ better understanding of what this means, try and drag the panels in the example
|
||||
|
||||
### Group Controls Panel
|
||||
|
||||
`DockviewReact` accepts `leftHeaderActionsComponent` and `rightHeaderActionsComponent` which expect a React component with props `IDockviewHeaderActionsProps`.
|
||||
These controls are rendered of the left and right side of the space to the right of the tabs in the header bar.
|
||||
`DockviewReact` accepts `leftHeaderActionsComponent`, `rightHeaderActionsComponent` and `prefixHeaderActionsComponent` which expect a React component with props `IDockviewHeaderActionsProps`.
|
||||
These controls are rendered to left and right side of the space to the right of the tabs in the header bar as well as before the first tab in the case of the prefix header prop.
|
||||
|
||||
```tsx
|
||||
const Component: React.FunctionComponent<IDockviewHeaderActionsProps> = () => {
|
||||
|
@ -191,6 +191,23 @@ const LeftControls = (props: IDockviewHeaderActionsProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const PrefixHeaderControls = (props: IDockviewHeaderActionsProps) => {
|
||||
return (
|
||||
<div
|
||||
className="group-control"
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '0px 8px',
|
||||
height: '100%',
|
||||
color: 'var(--dv-activegroup-visiblepanel-tab-color)',
|
||||
}}
|
||||
>
|
||||
<Icon icon="Menu" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const DockviewDemo = (props: { theme?: string }) => {
|
||||
const onReady = (event: DockviewReadyEvent) => {
|
||||
event.api.addPanel({
|
||||
@ -262,6 +279,7 @@ const DockviewDemo = (props: { theme?: string }) => {
|
||||
defaultTabComponent={headerComponents.default}
|
||||
rightHeaderActionsComponent={RightControls}
|
||||
leftHeaderActionsComponent={LeftControls}
|
||||
prefixHeaderActionsComponent={PrefixHeaderControls}
|
||||
onReady={onReady}
|
||||
className={props.theme || 'dockview-theme-abyss'}
|
||||
/>
|
||||
|
Loading…
Reference in New Issue
Block a user