feat: tab panel overflow dropdown

This commit is contained in:
mathuo 2025-02-25 21:05:38 +00:00
parent 5754ddc3f4
commit 7a6b2cb26d
No known key found for this signature in database
GPG Key ID: C6EEDEFD6CA07281
15 changed files with 360 additions and 150 deletions

View File

@ -1,11 +1,12 @@
import { IDockviewPanelModel } from '../../dockview/dockviewPanelModel'; import { IDockviewPanelModel } from '../../dockview/dockviewPanelModel';
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel'; import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
import { import {
GroupPanelPartInitParameters, TabPartInitParameters,
IContentRenderer, IContentRenderer,
ITabRenderer, ITabRenderer,
} from '../../dockview/types'; } from '../../dockview/types';
import { PanelUpdateEvent } from '../../panel/types'; import { PanelUpdateEvent } from '../../panel/types';
import { TabLocation } from '../../dockview/framework';
export class DockviewPanelModelMock implements IDockviewPanelModel { export class DockviewPanelModelMock implements IDockviewPanelModel {
constructor( constructor(
@ -17,8 +18,11 @@ export class DockviewPanelModelMock implements IDockviewPanelModel {
// //
} }
copyTabComponent(tabLocation: TabLocation): ITabRenderer {
return this.tab;
}
init(params: GroupPanelPartInitParameters): void { init(params: TabPartInitParameters): void {
// //
} }

View File

@ -2453,17 +2453,17 @@ describe('dockviewComponent', () => {
const group = dockview.getGroupPanel('panel2')!.api.group; const group = dockview.getGroupPanel('panel2')!.api.group;
const viewQuery = group.element.querySelectorAll( const viewQuery = group.element.querySelectorAll(
'.dv-groupview > .dv-tabs-and-actions-container > .dv-tabs-container > .dv-tab' '.dv-groupview > .dv-tabs-and-actions-container > .dv-tabs-panel > .dv-tabs-container > .dv-tab'
); );
expect(viewQuery.length).toBe(2); expect(viewQuery.length).toBe(2);
const viewQuery2 = group.element.querySelectorAll( const viewQuery2 = group.element.querySelectorAll(
'.dv-groupview > .dv-tabs-and-actions-container > .dv-tabs-container > .dv-tab > .dv-default-tab' '.dv-groupview > .dv-tabs-and-actions-container > .dv-tabs-panel > .dv-tabs-container > .dv-tab > .dv-default-tab'
); );
expect(viewQuery2.length).toBe(1); expect(viewQuery2.length).toBe(1);
const viewQuery3 = group.element.querySelectorAll( const viewQuery3 = group.element.querySelectorAll(
'.dv-groupview > .dv-tabs-and-actions-container > .dv-tabs-container > .dv-tab > .panel-tab-part-panel2' '.dv-groupview > .dv-tabs-and-actions-container > .dv-tabs-panel > .dv-tabs-container > .dv-tab > .panel-tab-part-panel2'
); );
expect(viewQuery3.length).toBe(1); expect(viewQuery3.length).toBe(1);
}); });

View File

@ -24,6 +24,7 @@ import { createOffsetDragOverEvent } from '../__test_utils__/utils';
import { OverlayRenderContainer } from '../../overlay/overlayRenderContainer'; import { OverlayRenderContainer } from '../../overlay/overlayRenderContainer';
import { Emitter } from '../../events'; import { Emitter } from '../../events';
import { fromPartial } from '@total-typescript/shoehorn'; import { fromPartial } from '@total-typescript/shoehorn';
import { TabLocation } from '../../dockview/framework';
enum GroupChangeKind2 { enum GroupChangeKind2 {
ADD_PANEL, ADD_PANEL,
@ -36,12 +37,16 @@ class TestModel implements IDockviewPanelModel {
readonly contentComponent: string; readonly contentComponent: string;
readonly tab: ITabRenderer; readonly tab: ITabRenderer;
constructor(id: string) { constructor(readonly id: string) {
this.content = new TestHeaderPart(id); this.content = new TestHeaderPart(id);
this.contentComponent = id; this.contentComponent = id;
this.tab = new TestContentPart(id); this.tab = new TestContentPart(id);
} }
copyTabComponent(tabLocation: TabLocation): ITabRenderer {
return new TestHeaderPart(this.id);
}
update(event: PanelUpdateEvent): void { update(event: PanelUpdateEvent): void {
// //
} }

View File

@ -1,8 +1,35 @@
.dv-tabs-panel {
overflow: hidden;
&.dv-horizontal {
.dv-tabs-container {
.dv-tab {
&:last-child {
margin-right: 0;
}
&:not(:nth-last-child(1)) {
margin-left: 0;
}
&:not(:first-child)::before {
content: ' ';
position: absolute;
top: 0;
left: 0;
z-index: 5;
pointer-events: none;
background-color: var(--dv-tab-divider-color);
width: 1px;
height: 100%;
}
}
}
}
.dv-tabs-container { .dv-tabs-container {
display: flex; display: flex;
overflow-x: overlay; overflow: hidden;
overflow-y: hidden;
scrollbar-width: thin; // firefox scrollbar-width: thin; // firefox
&::-webkit-scrollbar { &::-webkit-scrollbar {
@ -26,36 +53,55 @@
cursor: pointer; cursor: pointer;
position: relative; position: relative;
box-sizing: border-box; box-sizing: border-box;
font-size: var(-dv-tab-font-size); font-size: var(--dv-tab-font-size);
margin: var(--dv-tab-margin); margin: var(--dv-tab-margin);
}
&:first-child {
margin-right: 0;
} }
&:not(:nth-last-child(1)) { .dv-tabs-overflow-dropdown-default {
margin-left: 0; background-color: var(
} --dv-activegroup-hiddenpanel-tab-background-color
);
&:not(:first-child)::before {
content: ' ';
position: absolute;
top: 0;
left: 0;
z-index: 5;
pointer-events: none;
background-color: var(--dv-tab-divider-color);
width: 1px;
height: 100%; height: 100%;
color: var(--dv-activegroup-hiddenpanel-tab-color);
border-left: 1px solid var(--dv-tab-divider-color);
margin: var(--dv-tab-margin);
display: flex;
align-items: center;
flex-shrink: 0;
padding: 0.25rem 0.5rem;
cursor: pointer;
> span {
padding-left: 0.25rem;
}
> svg {
transform: rotate(90deg);
}
} }
} }
&.dv-tabs-overflow-container { .dv-tabs-overflow-container {
flex-direction: column; flex-direction: column;
height: unset; height: unset;
border: 1px solid var(--dv-tab-divider-color);
background-color: var(--dv-group-view-background-color);
.dv-tab { .dv-tab {
height: var(--dv-tabs-and-actions-container-height); -webkit-user-drag: element;
outline: none;
padding: 0.25rem 0.5rem;
cursor: pointer;
position: relative;
box-sizing: border-box;
font-size: var(--dv-tab-font-size);
margin: var(--dv-tab-margin);
&:not(:last-child) {
border-bottom: 1px solid var(--dv-tab-divider-color);
}
} }
.dv-active-tab { .dv-active-tab {
@ -71,13 +117,3 @@
color: var(--dv-activegroup-hiddenpanel-tab-color); color: var(--dv-activegroup-hiddenpanel-tab-color);
} }
} }
}
.dv-tabs-panel {
.dv-tabs-overflow-handle {
height: 100%;
width: 15px;
flex-shrink: 0;
background-color: red;
}
}

View File

@ -1,11 +1,17 @@
import { getPanelData } from '../../../dnd/dataTransfer'; import { getPanelData } from '../../../dnd/dataTransfer';
import { OverflowObserver } from '../../../dom'; import {
isChildEntirelyVisibleWithinParent,
OverflowObserver,
toggleClass,
} from '../../../dom';
import { addDisposableListener, Emitter, Event } from '../../../events'; import { addDisposableListener, Emitter, Event } from '../../../events';
import { import {
CompositeDisposable, CompositeDisposable,
Disposable, Disposable,
IValueDisposable, IValueDisposable,
MutableDisposable,
} from '../../../lifecycle'; } from '../../../lifecycle';
import { createChevronRightButton } from '../../../svg';
import { DockviewComponent } from '../../dockviewComponent'; import { DockviewComponent } from '../../dockviewComponent';
import { DockviewGroupPanel } from '../../dockviewGroupPanel'; import { DockviewGroupPanel } from '../../dockviewGroupPanel';
import { WillShowOverlayLocationEvent } from '../../dockviewGroupPanelModel'; import { WillShowOverlayLocationEvent } from '../../dockviewGroupPanelModel';
@ -13,14 +19,38 @@ import { DockviewPanel, IDockviewPanel } from '../../dockviewPanel';
import { Tab } from '../tab/tab'; import { Tab } from '../tab/tab';
import { TabDragEvent, TabDropIndexEvent } from './tabsContainer'; import { TabDragEvent, TabDropIndexEvent } from './tabsContainer';
type DropdownElement = {
element: HTMLElement;
update: (params: { tabs: number }) => void;
dispose?: () => void;
};
function createDropdownElementHandle(): DropdownElement {
const el = document.createElement('div');
el.className = 'dv-tabs-overflow-dropdown-default';
const text = document.createElement('span');
text.textContent = ``;
const icon = createChevronRightButton();
el.appendChild(icon);
el.appendChild(text);
return {
element: el,
update: (params: { tabs: number }) => {
text.textContent = `${params.tabs}`;
},
};
}
export class Tabs extends CompositeDisposable { export class Tabs extends CompositeDisposable {
private readonly _element: HTMLElement; private readonly _element: HTMLElement;
private readonly _tabsList: HTMLElement; private readonly _tabsList: HTMLElement;
private tabs: IValueDisposable<Tab>[] = []; private tabs: IValueDisposable<Tab>[] = [];
private selectedIndex = -1; private selectedIndex = -1;
private _hasOverflow = false;
private _dropdownAnchor: HTMLElement | null = null; private readonly _dropdownDisposable = new MutableDisposable();
private readonly _onTabDragStart = new Emitter<TabDragEvent>(); private readonly _onTabDragStart = new Emitter<TabDragEvent>();
readonly onTabDragStart: Event<TabDragEvent> = this._onTabDragStart.event; readonly onTabDragStart: Event<TabDragEvent> = this._onTabDragStart.event;
@ -33,6 +63,9 @@ export class Tabs extends CompositeDisposable {
readonly onWillShowOverlay: Event<WillShowOverlayLocationEvent> = readonly onWillShowOverlay: Event<WillShowOverlayLocationEvent> =
this._onWillShowOverlay.event; this._onWillShowOverlay.event;
private dropdownPart: DropdownElement | null = null;
private _overflowTabs: string[] = [];
get element(): HTMLElement { get element(): HTMLElement {
return this._element; return this._element;
} }
@ -52,7 +85,7 @@ export class Tabs extends CompositeDisposable {
super(); super();
this._element = document.createElement('div'); this._element = document.createElement('div');
this._element.className = 'dv-tabs-panel'; this._element.className = 'dv-tabs-panel dv-horizontal';
this._element.style.display = 'flex'; this._element.style.display = 'flex';
this._element.style.overflow = 'auto'; this._element.style.overflow = 'auto';
this._tabsList = document.createElement('div'); this._tabsList = document.createElement('div');
@ -62,12 +95,17 @@ export class Tabs extends CompositeDisposable {
const observer = new OverflowObserver(this._tabsList); const observer = new OverflowObserver(this._tabsList);
this.addDisposables( this.addDisposables(
this._dropdownDisposable,
this._onWillShowOverlay,
this._onDrop,
this._onTabDragStart,
observer, observer,
observer.onDidChange((event) => { observer.onDidChange((event) => {
const hasOverflow = event.hasScrollX || event.hasScrollY; const hasOverflow = event.hasScrollX || event.hasScrollY;
if (this._hasOverflow !== hasOverflow) { this.toggleDropdown({ reset: !hasOverflow });
this.toggleDropdown(hasOverflow); }),
} addDisposableListener(this._tabsList, 'scroll', () => {
this.toggleDropdown({ reset: false });
}), }),
addDisposableListener(this.element, 'pointerdown', (event) => { addDisposableListener(this.element, 'pointerdown', (event) => {
if (event.defaultPrevented) { if (event.defaultPrevented) {
@ -103,10 +141,27 @@ export class Tabs extends CompositeDisposable {
} }
setActivePanel(panel: IDockviewPanel): void { setActivePanel(panel: IDockviewPanel): void {
this.tabs.forEach((tab) => { let runningWidth = 0;
for (const tab of this.tabs) {
const isActivePanel = panel.id === tab.value.panel.id; const isActivePanel = panel.id === tab.value.panel.id;
tab.value.setActive(isActivePanel); tab.value.setActive(isActivePanel);
});
if (isActivePanel) {
const element = tab.value.element;
const parentElement = element.parentElement!;
if (
runningWidth < parentElement.scrollLeft ||
runningWidth + element.clientWidth >
parentElement.scrollLeft + parentElement.clientWidth
) {
parentElement.scrollLeft = runningWidth;
}
}
runningWidth += tab.value.element.clientWidth;
}
} }
openPanel(panel: IDockviewPanel, index: number = this.tabs.length): void { openPanel(panel: IDockviewPanel, index: number = this.tabs.length): void {
@ -120,7 +175,11 @@ export class Tabs extends CompositeDisposable {
tab.onDragStart((event) => { tab.onDragStart((event) => {
this._onTabDragStart.fire({ nativeEvent: event, panel }); this._onTabDragStart.fire({ nativeEvent: event, panel });
}), }),
tab.onChanged((event) => { tab.onPointerDown((event) => {
if (event.defaultPrevented) {
return;
}
const isFloatingGroupsEnabled = const isFloatingGroupsEnabled =
!this.accessor.options.disableFloatingGroups; !this.accessor.options.disableFloatingGroups;
@ -149,15 +208,13 @@ export class Tabs extends CompositeDisposable {
return; return;
} }
const isLeftClick = event.button === 0; switch (event.button) {
case 0: // left click or touch
if (!isLeftClick || event.defaultPrevented) {
return;
}
if (this.group.activePanel !== panel) { if (this.group.activePanel !== panel) {
this.group.model.openPanel(panel); this.group.model.openPanel(panel);
} }
break;
}
}), }),
tab.onDrop((event) => { tab.onDrop((event) => {
this._onDrop.fire({ this._onDrop.fire({
@ -218,33 +275,89 @@ export class Tabs extends CompositeDisposable {
} }
} }
private toggleDropdown(show: boolean): void { private toggleDropdown(options: { reset: boolean }): void {
this._hasOverflow = show; const tabs = options.reset
? []
: this.tabs
.filter(
(tab) =>
!isChildEntirelyVisibleWithinParent(
tab.value.element,
this._tabsList
)
)
.map((x) => x.value.panel.id);
if (this._dropdownAnchor) { this._overflowTabs = tabs;
this._dropdownAnchor.remove();
this._dropdownAnchor = null;
}
if (!show) { if (this._overflowTabs.length > 0 && this.dropdownPart) {
this.dropdownPart.update({ tabs: tabs.length });
return; return;
} }
this._dropdownAnchor = document.createElement('div'); if (this._overflowTabs.length === 0) {
this._dropdownAnchor.className = 'dv-tabs-overflow-handle'; this._dropdownDisposable.dispose();
return;
}
this.element.appendChild(this._dropdownAnchor); const root = document.createElement('div');
root.className = 'dv-tabs-overflow-dropdown-root';
addDisposableListener(this._dropdownAnchor, 'click', (event) => { const part = createDropdownElementHandle();
part.update({ tabs: tabs.length });
this.dropdownPart = part;
root.appendChild(part.element);
this.element.appendChild(root);
this._dropdownDisposable.value = new CompositeDisposable(
Disposable.from(() => {
root.remove();
this.dropdownPart?.dispose?.();
this.dropdownPart = null;
}),
addDisposableListener(
root,
'pointerdown',
(event) => {
event.preventDefault();
},
{ capture: true }
),
addDisposableListener(root, 'click', (event) => {
const el = document.createElement('div'); const el = document.createElement('div');
el.style.overflow = 'auto'; el.style.overflow = 'auto';
el.className = el.className = 'dv-tabs-overflow-container';
'dv-tabs-and-actions-container dv-tabs-container dv-tabs-overflow-container';
this.tabs.map((tab) => { this.tabs
const child = tab.value.element.cloneNode(true); .filter((tab) =>
this._overflowTabs.includes(tab.value.panel.id)
)
.map((tab) => {
const panelObject = this.group.panels.find(
(panel) => panel === tab.value.panel
)!;
const tabComponent =
panelObject.view.createTabRenderer(
'headerOverflow'
);
const child = tabComponent.element;
const wrapper = document.createElement('div'); const wrapper = document.createElement('div');
toggleClass(wrapper, 'dv-tab', true);
toggleClass(
wrapper,
'dv-active-tab',
panelObject.api.isActive
);
toggleClass(
wrapper,
'dv-inactive-tab',
!panelObject.api.isActive
);
wrapper.addEventListener('mousedown', () => { wrapper.addEventListener('mousedown', () => {
this.accessor.popupService.close(); this.accessor.popupService.close();
@ -260,6 +373,7 @@ export class Tabs extends CompositeDisposable {
x: event.clientX, x: event.clientX,
y: event.clientY, y: event.clientY,
}); });
}); })
);
} }
} }

View File

@ -140,6 +140,7 @@ export class TabsContainer
this._element.appendChild(this.rightActionsContainer); this._element.appendChild(this.rightActionsContainer);
this.addDisposables( this.addDisposables(
this.tabs,
this._onWillShowOverlay, this._onWillShowOverlay,
this._onDrop, this._onDrop,
this._onGroupDragStart, this._onGroupDragStart,
@ -171,6 +172,10 @@ export class TabsContainer
this.voidContainer.element, this.voidContainer.element,
'pointerdown', 'pointerdown',
(event) => { (event) => {
if (event.defaultPrevented) {
return;
}
const isFloatingGroupsEnabled = const isFloatingGroupsEnabled =
!this.accessor.options.disableFloatingGroups; !this.accessor.options.disableFloatingGroups;

View File

@ -4,27 +4,29 @@ import {
IContentRenderer, IContentRenderer,
ITabRenderer, ITabRenderer,
} from './types'; } from './types';
import { DockviewGroupPanel } from './dockviewGroupPanel';
import { IDisposable } from '../lifecycle'; import { IDisposable } from '../lifecycle';
import { IDockviewComponent } from './dockviewComponent'; import { IDockviewComponent } from './dockviewComponent';
import { PanelUpdateEvent } from '../panel/types'; import { PanelUpdateEvent } from '../panel/types';
import { TabLocation } from './framework';
export interface IDockviewPanelModel extends IDisposable { export interface IDockviewPanelModel extends IDisposable {
readonly contentComponent: string; readonly contentComponent: string;
readonly tabComponent?: string; readonly tabComponent?: string;
readonly content: IContentRenderer; readonly content: IContentRenderer;
readonly tab: ITabRenderer; readonly tab: ITabRenderer;
readonly newTab: ITabRenderer;
update(event: PanelUpdateEvent): void; update(event: PanelUpdateEvent): void;
layout(width: number, height: number): void; layout(width: number, height: number): void;
init(params: GroupPanelPartInitParameters): void; init(params: GroupPanelPartInitParameters): void;
updateParentGroup(group: DockviewGroupPanel, isPanelVisible: boolean): void; createTabRenderer(tabLocation: TabLocation): ITabRenderer;
} }
export class DockviewPanelModel implements IDockviewPanelModel { export class DockviewPanelModel implements IDockviewPanelModel {
private readonly _content: IContentRenderer; private readonly _content: IContentRenderer;
private readonly _tab: ITabRenderer; private readonly _tab: ITabRenderer;
private _params: GroupPanelPartInitParameters | undefined;
private _updateEvent: PanelUpdateEvent | undefined;
get content(): IContentRenderer { get content(): IContentRenderer {
return this._content; return this._content;
} }
@ -43,21 +45,23 @@ export class DockviewPanelModel implements IDockviewPanelModel {
this._tab = this.createTabComponent(this.id, tabComponent); this._tab = this.createTabComponent(this.id, tabComponent);
} }
get newTab() { createTabRenderer(tabLocation: TabLocation): ITabRenderer {
const cmp = this.createTabComponent(this.id, this.tabComponent); const cmp = this.createTabComponent(this.id, this.tabComponent);
if (this._params) {
cmp.init({ ...this._params, tabLocation });
}
if (this._updateEvent) {
cmp.update?.(this._updateEvent);
}
return cmp; return cmp;
} }
init(params: GroupPanelPartInitParameters): void { init(params: GroupPanelPartInitParameters): void {
this.content.init(params); this._params = params;
this.tab.init(params);
}
updateParentGroup( this.content.init(params);
_group: DockviewGroupPanel, this.tab.init({ ...params, tabLocation: 'header' });
_isPanelVisible: boolean
): void {
// noop
} }
layout(width: number, height: number): void { layout(width: number, height: number): void {
@ -65,6 +69,8 @@ export class DockviewPanelModel implements IDockviewPanelModel {
} }
update(event: PanelUpdateEvent): void { update(event: PanelUpdateEvent): void {
this._updateEvent = event;
this.content.update?.(event); this.content.update?.(event);
this.tab.update?.(event); this.tab.update?.(event);
} }

View File

@ -11,9 +11,11 @@ export interface IGroupPanelBaseProps<T extends { [index: string]: any } = any>
containerApi: DockviewApi; containerApi: DockviewApi;
} }
export type TabLocation = 'header' | 'headerOverflow';
export type IDockviewPanelHeaderProps< export type IDockviewPanelHeaderProps<
T extends { [index: string]: any } = any T extends { [index: string]: any } = any
> = IGroupPanelBaseProps<T>; > = IGroupPanelBaseProps<T> & { tabLocation: TabLocation };
export type IDockviewPanelProps<T extends { [index: string]: any } = any> = export type IDockviewPanelProps<T extends { [index: string]: any } = any> =
IGroupPanelBaseProps<T>; IGroupPanelBaseProps<T>;

View File

@ -4,6 +4,7 @@ import { DockviewApi } from '../api/component.api';
import { Optional } from '../types'; import { Optional } from '../types';
import { IDockviewGroupPanel } from './dockviewGroupPanel'; import { IDockviewGroupPanel } from './dockviewGroupPanel';
import { DockviewPanelRenderer } from '../overlay/overlayRenderContainer'; import { DockviewPanelRenderer } from '../overlay/overlayRenderContainer';
import { TabLocation } from './framework';
export interface HeaderPartInitParameters { export interface HeaderPartInitParameters {
title: string; title: string;
@ -34,10 +35,14 @@ export interface IWatermarkRenderer
init: (params: WatermarkRendererInitParameters) => void; init: (params: WatermarkRendererInitParameters) => void;
} }
export interface TabPartInitParameters extends GroupPanelPartInitParameters {
tabLocation: TabLocation;
}
export interface ITabRenderer export interface ITabRenderer
extends Optional<Omit<IPanel, 'id'>, RendererMethodOptionalList> { extends Optional<Omit<IPanel, 'id'>, RendererMethodOptionalList> {
readonly element: HTMLElement; readonly element: HTMLElement;
init(parameters: GroupPanelPartInitParameters): void; init(parameters: TabPartInitParameters): void;
} }
export interface IContentRenderer export interface IContentRenderer

View File

@ -357,3 +357,25 @@ export class Classnames {
} }
} }
} }
export function isChildEntirelyVisibleWithinParent(
child: HTMLElement,
parent: HTMLElement
): boolean {
//
const childPosition = getDomNodePagePosition(child);
const parentPosition = getDomNodePagePosition(parent);
if (childPosition.left < parentPosition.left) {
return false;
}
if (
childPosition.left + childPosition.width >
parentPosition.left + parentPosition.width
) {
return false;
}
return true;
}

View File

@ -11,6 +11,9 @@ import { Classnames } from '../dom';
const nextLayoutId = sequentialNumberGenerator(); const nextLayoutId = sequentialNumberGenerator();
/**
* A direction in which a panel can be moved or placed relative to another panel.
*/
export type Direction = 'left' | 'right' | 'above' | 'below' | 'within'; export type Direction = 'left' | 'right' | 'above' | 'below' | 'within';
export function toTarget(direction: Direction): Position { export function toTarget(direction: Direction): Position {

View File

@ -4,8 +4,7 @@
--dv-tab-margin: 0.5rem 0.25rem; --dv-tab-margin: 0.5rem 0.25rem;
--dv-tabs-and-actions-container-height: 44px; --dv-tabs-and-actions-container-height: 44px;
--dv-border-radius: 20px;
--dv-border-radius
.dv-resize-container:has(> .dv-groupview) { .dv-resize-container:has(> .dv-groupview) {
border-radius: 8px; border-radius: 8px;
@ -27,11 +26,11 @@
border: none; border: none;
} }
.dv-groupview { .dv-tabs-overflow-container,
border-radius: var(--dv-border-radius); .dv-tabs-overflow-dropdown-default {
border-radius: 8px;
.dv-tabs-and-actions-container { height: unset !important;
padding: 0px calc(var(--dv-border-radius) / 2); }
.dv-tab { .dv-tab {
border-radius: 8px; border-radius: 8px;
@ -41,6 +40,12 @@
width: 8px; width: 8px;
} }
} }
.dv-groupview {
border-radius: var(--dv-border-radius);
.dv-tabs-and-actions-container {
padding: 0px calc(var(--dv-border-radius) / 2);
} }
.dv-content-container { .dv-content-container {

View File

@ -2,7 +2,6 @@ import type {
DockviewApi, DockviewApi,
DockviewGroupPanel, DockviewGroupPanel,
DockviewPanelApi, DockviewPanelApi,
GroupPanelPartInitParameters,
IContentRenderer, IContentRenderer,
IDockviewPanelHeaderProps, IDockviewPanelHeaderProps,
IGroupHeaderProps, IGroupHeaderProps,
@ -12,6 +11,7 @@ import type {
IWatermarkRenderer, IWatermarkRenderer,
PanelUpdateEvent, PanelUpdateEvent,
Parameters, Parameters,
TabPartInitParameters,
WatermarkRendererInitParameters, WatermarkRendererInitParameters,
} from 'dockview-core'; } from 'dockview-core';
import { import {
@ -121,7 +121,7 @@ export class VueRenderer
private _api: DockviewPanelApi | undefined; private _api: DockviewPanelApi | undefined;
private _containerApi: DockviewApi | undefined; private _containerApi: DockviewApi | undefined;
init(parameters: GroupPanelPartInitParameters): void { init(parameters: TabPartInitParameters): void {
this._api = parameters.api; this._api = parameters.api;
this._containerApi = parameters.containerApi; this._containerApi = parameters.containerApi;
@ -129,6 +129,7 @@ export class VueRenderer
params: parameters.params, params: parameters.params,
api: parameters.api, api: parameters.api,
containerApi: parameters.containerApi, containerApi: parameters.containerApi,
tabLocation: parameters.tabLocation,
}; };
this._renderDisposable?.dispose(); this._renderDisposable?.dispose();

View File

@ -35,6 +35,7 @@ export const DockviewDefaultTab: React.FunctionComponent<
onPointerDown, onPointerDown,
onPointerUp, onPointerUp,
onPointerLeave, onPointerLeave,
tabLocation,
...rest ...rest
}) => { }) => {
const title = useTitle(api); const title = useTitle(api);
@ -96,7 +97,7 @@ export const DockviewDefaultTab: React.FunctionComponent<
className="dv-default-tab" className="dv-default-tab"
> >
<span className="dv-default-tab-content">{title}</span> <span className="dv-default-tab-content">{title}</span>
{!hideClose && ( {!hideClose && tabLocation !== 'headerOverflow' && (
<div <div
className="dv-default-tab-action" className="dv-default-tab-action"
onPointerDown={onBtnPointerDown} onPointerDown={onBtnPointerDown}

View File

@ -3,13 +3,13 @@ import { ReactPart, ReactPortalStore } from '../react';
import { import {
PanelUpdateEvent, PanelUpdateEvent,
ITabRenderer, ITabRenderer,
GroupPanelPartInitParameters, TabPartInitParameters,
IGroupPanelBaseProps, IDockviewPanelHeaderProps,
} from 'dockview-core'; } from 'dockview-core';
export class ReactPanelHeaderPart implements ITabRenderer { export class ReactPanelHeaderPart implements ITabRenderer {
private readonly _element: HTMLElement; private readonly _element: HTMLElement;
private part?: ReactPart<IGroupPanelBaseProps>; private part?: ReactPart<IDockviewPanelHeaderProps>;
get element(): HTMLElement { get element(): HTMLElement {
return this._element; return this._element;
@ -17,7 +17,7 @@ export class ReactPanelHeaderPart implements ITabRenderer {
constructor( constructor(
public readonly id: string, public readonly id: string,
private readonly component: React.FunctionComponent<IGroupPanelBaseProps>, private readonly component: React.FunctionComponent<IDockviewPanelHeaderProps>,
private readonly reactPortalStore: ReactPortalStore private readonly reactPortalStore: ReactPortalStore
) { ) {
this._element = document.createElement('div'); this._element = document.createElement('div');
@ -30,7 +30,7 @@ export class ReactPanelHeaderPart implements ITabRenderer {
//noop //noop
} }
public init(parameters: GroupPanelPartInitParameters): void { public init(parameters: TabPartInitParameters): void {
this.part = new ReactPart( this.part = new ReactPart(
this.element, this.element,
this.reactPortalStore, this.reactPortalStore,
@ -39,6 +39,7 @@ export class ReactPanelHeaderPart implements ITabRenderer {
params: parameters.params, params: parameters.params,
api: parameters.api, api: parameters.api,
containerApi: parameters.containerApi, containerApi: parameters.containerApi,
tabLocation: parameters.tabLocation,
} }
); );
} }