mirror of
https://github.com/mathuo/dockview
synced 2025-03-09 23:42:05 +00:00
feat: tab panel overflow dropdown
This commit is contained in:
parent
5754ddc3f4
commit
7a6b2cb26d
@ -1,11 +1,12 @@
|
||||
import { IDockviewPanelModel } from '../../dockview/dockviewPanelModel';
|
||||
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
|
||||
import {
|
||||
GroupPanelPartInitParameters,
|
||||
TabPartInitParameters,
|
||||
IContentRenderer,
|
||||
ITabRenderer,
|
||||
} from '../../dockview/types';
|
||||
import { PanelUpdateEvent } from '../../panel/types';
|
||||
import { TabLocation } from '../../dockview/framework';
|
||||
|
||||
export class DockviewPanelModelMock implements IDockviewPanelModel {
|
||||
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 {
|
||||
//
|
||||
}
|
||||
|
||||
|
@ -2453,17 +2453,17 @@ describe('dockviewComponent', () => {
|
||||
const group = dockview.getGroupPanel('panel2')!.api.group;
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
});
|
||||
|
@ -24,6 +24,7 @@ import { createOffsetDragOverEvent } from '../__test_utils__/utils';
|
||||
import { OverlayRenderContainer } from '../../overlay/overlayRenderContainer';
|
||||
import { Emitter } from '../../events';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
import { TabLocation } from '../../dockview/framework';
|
||||
|
||||
enum GroupChangeKind2 {
|
||||
ADD_PANEL,
|
||||
@ -36,12 +37,16 @@ class TestModel implements IDockviewPanelModel {
|
||||
readonly contentComponent: string;
|
||||
readonly tab: ITabRenderer;
|
||||
|
||||
constructor(id: string) {
|
||||
constructor(readonly id: string) {
|
||||
this.content = new TestHeaderPart(id);
|
||||
this.contentComponent = id;
|
||||
this.tab = new TestContentPart(id);
|
||||
}
|
||||
|
||||
copyTabComponent(tabLocation: TabLocation): ITabRenderer {
|
||||
return new TestHeaderPart(this.id);
|
||||
}
|
||||
|
||||
update(event: PanelUpdateEvent): void {
|
||||
//
|
||||
}
|
||||
|
@ -1,23 +1,93 @@
|
||||
.dv-tabs-container {
|
||||
display: flex;
|
||||
overflow-x: overlay;
|
||||
overflow-y: hidden;
|
||||
.dv-tabs-panel {
|
||||
overflow: hidden;
|
||||
|
||||
scrollbar-width: thin; // firefox
|
||||
&.dv-horizontal {
|
||||
.dv-tabs-container {
|
||||
.dv-tab {
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
height: 3px;
|
||||
&: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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Track */
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
.dv-tabs-container {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
scrollbar-width: thin; // firefox
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
height: 3px;
|
||||
}
|
||||
|
||||
/* Track */
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* Handle */
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--dv-tabs-container-scrollbar-color);
|
||||
}
|
||||
|
||||
.dv-tab {
|
||||
-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);
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle */
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--dv-tabs-container-scrollbar-color);
|
||||
.dv-tabs-overflow-dropdown-default {
|
||||
background-color: var(
|
||||
--dv-activegroup-hiddenpanel-tab-background-color
|
||||
);
|
||||
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 {
|
||||
flex-direction: column;
|
||||
height: unset;
|
||||
border: 1px solid var(--dv-tab-divider-color);
|
||||
background-color: var(--dv-group-view-background-color);
|
||||
|
||||
.dv-tab {
|
||||
-webkit-user-drag: element;
|
||||
@ -26,58 +96,24 @@
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
font-size: var(-dv-tab-font-size);
|
||||
font-size: var(--dv-tab-font-size);
|
||||
margin: var(--dv-tab-margin);
|
||||
|
||||
&:first-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%;
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid var(--dv-tab-divider-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.dv-tabs-overflow-container {
|
||||
flex-direction: column;
|
||||
height: unset;
|
||||
|
||||
.dv-tab {
|
||||
height: var(--dv-tabs-and-actions-container-height);
|
||||
}
|
||||
|
||||
.dv-active-tab {
|
||||
background-color: var(
|
||||
--dv-activegroup-visiblepanel-tab-background-color
|
||||
);
|
||||
color: var(--dv-activegroup-visiblepanel-tab-color);
|
||||
}
|
||||
.dv-inactive-tab {
|
||||
background-color: var(
|
||||
--dv-activegroup-hiddenpanel-tab-background-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;
|
||||
.dv-active-tab {
|
||||
background-color: var(
|
||||
--dv-activegroup-visiblepanel-tab-background-color
|
||||
);
|
||||
color: var(--dv-activegroup-visiblepanel-tab-color);
|
||||
}
|
||||
.dv-inactive-tab {
|
||||
background-color: var(
|
||||
--dv-activegroup-hiddenpanel-tab-background-color
|
||||
);
|
||||
color: var(--dv-activegroup-hiddenpanel-tab-color);
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,17 @@
|
||||
import { getPanelData } from '../../../dnd/dataTransfer';
|
||||
import { OverflowObserver } from '../../../dom';
|
||||
import {
|
||||
isChildEntirelyVisibleWithinParent,
|
||||
OverflowObserver,
|
||||
toggleClass,
|
||||
} from '../../../dom';
|
||||
import { addDisposableListener, Emitter, Event } from '../../../events';
|
||||
import {
|
||||
CompositeDisposable,
|
||||
Disposable,
|
||||
IValueDisposable,
|
||||
MutableDisposable,
|
||||
} from '../../../lifecycle';
|
||||
import { createChevronRightButton } from '../../../svg';
|
||||
import { DockviewComponent } from '../../dockviewComponent';
|
||||
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
|
||||
import { WillShowOverlayLocationEvent } from '../../dockviewGroupPanelModel';
|
||||
@ -13,14 +19,38 @@ import { DockviewPanel, IDockviewPanel } from '../../dockviewPanel';
|
||||
import { Tab } from '../tab/tab';
|
||||
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 {
|
||||
private readonly _element: HTMLElement;
|
||||
private readonly _tabsList: HTMLElement;
|
||||
|
||||
private tabs: IValueDisposable<Tab>[] = [];
|
||||
private selectedIndex = -1;
|
||||
private _hasOverflow = false;
|
||||
private _dropdownAnchor: HTMLElement | null = null;
|
||||
|
||||
private readonly _dropdownDisposable = new MutableDisposable();
|
||||
|
||||
private readonly _onTabDragStart = new Emitter<TabDragEvent>();
|
||||
readonly onTabDragStart: Event<TabDragEvent> = this._onTabDragStart.event;
|
||||
@ -33,6 +63,9 @@ export class Tabs extends CompositeDisposable {
|
||||
readonly onWillShowOverlay: Event<WillShowOverlayLocationEvent> =
|
||||
this._onWillShowOverlay.event;
|
||||
|
||||
private dropdownPart: DropdownElement | null = null;
|
||||
private _overflowTabs: string[] = [];
|
||||
|
||||
get element(): HTMLElement {
|
||||
return this._element;
|
||||
}
|
||||
@ -52,7 +85,7 @@ export class Tabs extends CompositeDisposable {
|
||||
super();
|
||||
|
||||
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.overflow = 'auto';
|
||||
this._tabsList = document.createElement('div');
|
||||
@ -62,12 +95,17 @@ export class Tabs extends CompositeDisposable {
|
||||
const observer = new OverflowObserver(this._tabsList);
|
||||
|
||||
this.addDisposables(
|
||||
this._dropdownDisposable,
|
||||
this._onWillShowOverlay,
|
||||
this._onDrop,
|
||||
this._onTabDragStart,
|
||||
observer,
|
||||
observer.onDidChange((event) => {
|
||||
const hasOverflow = event.hasScrollX || event.hasScrollY;
|
||||
if (this._hasOverflow !== hasOverflow) {
|
||||
this.toggleDropdown(hasOverflow);
|
||||
}
|
||||
this.toggleDropdown({ reset: !hasOverflow });
|
||||
}),
|
||||
addDisposableListener(this._tabsList, 'scroll', () => {
|
||||
this.toggleDropdown({ reset: false });
|
||||
}),
|
||||
addDisposableListener(this.element, 'pointerdown', (event) => {
|
||||
if (event.defaultPrevented) {
|
||||
@ -103,10 +141,27 @@ export class Tabs extends CompositeDisposable {
|
||||
}
|
||||
|
||||
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;
|
||||
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 {
|
||||
@ -120,7 +175,11 @@ export class Tabs extends CompositeDisposable {
|
||||
tab.onDragStart((event) => {
|
||||
this._onTabDragStart.fire({ nativeEvent: event, panel });
|
||||
}),
|
||||
tab.onChanged((event) => {
|
||||
tab.onPointerDown((event) => {
|
||||
if (event.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isFloatingGroupsEnabled =
|
||||
!this.accessor.options.disableFloatingGroups;
|
||||
|
||||
@ -149,14 +208,12 @@ export class Tabs extends CompositeDisposable {
|
||||
return;
|
||||
}
|
||||
|
||||
const isLeftClick = event.button === 0;
|
||||
|
||||
if (!isLeftClick || event.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.group.activePanel !== panel) {
|
||||
this.group.model.openPanel(panel);
|
||||
switch (event.button) {
|
||||
case 0: // left click or touch
|
||||
if (this.group.activePanel !== panel) {
|
||||
this.group.model.openPanel(panel);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}),
|
||||
tab.onDrop((event) => {
|
||||
@ -218,48 +275,105 @@ export class Tabs extends CompositeDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
private toggleDropdown(show: boolean): void {
|
||||
this._hasOverflow = show;
|
||||
private toggleDropdown(options: { reset: boolean }): void {
|
||||
const tabs = options.reset
|
||||
? []
|
||||
: this.tabs
|
||||
.filter(
|
||||
(tab) =>
|
||||
!isChildEntirelyVisibleWithinParent(
|
||||
tab.value.element,
|
||||
this._tabsList
|
||||
)
|
||||
)
|
||||
.map((x) => x.value.panel.id);
|
||||
|
||||
if (this._dropdownAnchor) {
|
||||
this._dropdownAnchor.remove();
|
||||
this._dropdownAnchor = null;
|
||||
}
|
||||
this._overflowTabs = tabs;
|
||||
|
||||
if (!show) {
|
||||
if (this._overflowTabs.length > 0 && this.dropdownPart) {
|
||||
this.dropdownPart.update({ tabs: tabs.length });
|
||||
return;
|
||||
}
|
||||
|
||||
this._dropdownAnchor = document.createElement('div');
|
||||
this._dropdownAnchor.className = 'dv-tabs-overflow-handle';
|
||||
if (this._overflowTabs.length === 0) {
|
||||
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 el = document.createElement('div');
|
||||
el.style.overflow = 'auto';
|
||||
el.className =
|
||||
'dv-tabs-and-actions-container dv-tabs-container dv-tabs-overflow-container';
|
||||
const part = createDropdownElementHandle();
|
||||
part.update({ tabs: tabs.length });
|
||||
|
||||
this.tabs.map((tab) => {
|
||||
const child = tab.value.element.cloneNode(true);
|
||||
this.dropdownPart = part;
|
||||
|
||||
const wrapper = document.createElement('div');
|
||||
root.appendChild(part.element);
|
||||
this.element.appendChild(root);
|
||||
|
||||
wrapper.addEventListener('mousedown', () => {
|
||||
this.accessor.popupService.close();
|
||||
tab.value.element.scrollIntoView();
|
||||
tab.value.panel.api.setActive();
|
||||
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');
|
||||
el.style.overflow = 'auto';
|
||||
el.className = 'dv-tabs-overflow-container';
|
||||
|
||||
this.tabs
|
||||
.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');
|
||||
toggleClass(wrapper, 'dv-tab', true);
|
||||
toggleClass(
|
||||
wrapper,
|
||||
'dv-active-tab',
|
||||
panelObject.api.isActive
|
||||
);
|
||||
toggleClass(
|
||||
wrapper,
|
||||
'dv-inactive-tab',
|
||||
!panelObject.api.isActive
|
||||
);
|
||||
|
||||
wrapper.addEventListener('mousedown', () => {
|
||||
this.accessor.popupService.close();
|
||||
tab.value.element.scrollIntoView();
|
||||
tab.value.panel.api.setActive();
|
||||
});
|
||||
wrapper.appendChild(child);
|
||||
|
||||
el.appendChild(wrapper);
|
||||
});
|
||||
|
||||
this.accessor.popupService.openPopover(el, {
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
});
|
||||
wrapper.appendChild(child);
|
||||
|
||||
el.appendChild(wrapper);
|
||||
});
|
||||
|
||||
this.accessor.popupService.openPopover(el, {
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
@ -140,6 +140,7 @@ export class TabsContainer
|
||||
this._element.appendChild(this.rightActionsContainer);
|
||||
|
||||
this.addDisposables(
|
||||
this.tabs,
|
||||
this._onWillShowOverlay,
|
||||
this._onDrop,
|
||||
this._onGroupDragStart,
|
||||
@ -171,6 +172,10 @@ export class TabsContainer
|
||||
this.voidContainer.element,
|
||||
'pointerdown',
|
||||
(event) => {
|
||||
if (event.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isFloatingGroupsEnabled =
|
||||
!this.accessor.options.disableFloatingGroups;
|
||||
|
||||
|
@ -4,27 +4,29 @@ import {
|
||||
IContentRenderer,
|
||||
ITabRenderer,
|
||||
} from './types';
|
||||
import { DockviewGroupPanel } from './dockviewGroupPanel';
|
||||
import { IDisposable } from '../lifecycle';
|
||||
import { IDockviewComponent } from './dockviewComponent';
|
||||
import { PanelUpdateEvent } from '../panel/types';
|
||||
import { TabLocation } from './framework';
|
||||
|
||||
export interface IDockviewPanelModel extends IDisposable {
|
||||
readonly contentComponent: string;
|
||||
readonly tabComponent?: string;
|
||||
readonly content: IContentRenderer;
|
||||
readonly tab: ITabRenderer;
|
||||
readonly newTab: ITabRenderer;
|
||||
update(event: PanelUpdateEvent): void;
|
||||
layout(width: number, height: number): void;
|
||||
init(params: GroupPanelPartInitParameters): void;
|
||||
updateParentGroup(group: DockviewGroupPanel, isPanelVisible: boolean): void;
|
||||
createTabRenderer(tabLocation: TabLocation): ITabRenderer;
|
||||
}
|
||||
|
||||
export class DockviewPanelModel implements IDockviewPanelModel {
|
||||
private readonly _content: IContentRenderer;
|
||||
private readonly _tab: ITabRenderer;
|
||||
|
||||
private _params: GroupPanelPartInitParameters | undefined;
|
||||
private _updateEvent: PanelUpdateEvent | undefined;
|
||||
|
||||
get content(): IContentRenderer {
|
||||
return this._content;
|
||||
}
|
||||
@ -43,21 +45,23 @@ export class DockviewPanelModel implements IDockviewPanelModel {
|
||||
this._tab = this.createTabComponent(this.id, tabComponent);
|
||||
}
|
||||
|
||||
get newTab() {
|
||||
createTabRenderer(tabLocation: TabLocation): ITabRenderer {
|
||||
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;
|
||||
}
|
||||
|
||||
init(params: GroupPanelPartInitParameters): void {
|
||||
this.content.init(params);
|
||||
this.tab.init(params);
|
||||
}
|
||||
this._params = params;
|
||||
|
||||
updateParentGroup(
|
||||
_group: DockviewGroupPanel,
|
||||
_isPanelVisible: boolean
|
||||
): void {
|
||||
// noop
|
||||
this.content.init(params);
|
||||
this.tab.init({ ...params, tabLocation: 'header' });
|
||||
}
|
||||
|
||||
layout(width: number, height: number): void {
|
||||
@ -65,6 +69,8 @@ export class DockviewPanelModel implements IDockviewPanelModel {
|
||||
}
|
||||
|
||||
update(event: PanelUpdateEvent): void {
|
||||
this._updateEvent = event;
|
||||
|
||||
this.content.update?.(event);
|
||||
this.tab.update?.(event);
|
||||
}
|
||||
|
@ -11,9 +11,11 @@ export interface IGroupPanelBaseProps<T extends { [index: string]: any } = any>
|
||||
containerApi: DockviewApi;
|
||||
}
|
||||
|
||||
export type TabLocation = 'header' | 'headerOverflow';
|
||||
|
||||
export type IDockviewPanelHeaderProps<
|
||||
T extends { [index: string]: any } = any
|
||||
> = IGroupPanelBaseProps<T>;
|
||||
> = IGroupPanelBaseProps<T> & { tabLocation: TabLocation };
|
||||
|
||||
export type IDockviewPanelProps<T extends { [index: string]: any } = any> =
|
||||
IGroupPanelBaseProps<T>;
|
||||
|
@ -4,6 +4,7 @@ import { DockviewApi } from '../api/component.api';
|
||||
import { Optional } from '../types';
|
||||
import { IDockviewGroupPanel } from './dockviewGroupPanel';
|
||||
import { DockviewPanelRenderer } from '../overlay/overlayRenderContainer';
|
||||
import { TabLocation } from './framework';
|
||||
|
||||
export interface HeaderPartInitParameters {
|
||||
title: string;
|
||||
@ -34,10 +35,14 @@ export interface IWatermarkRenderer
|
||||
init: (params: WatermarkRendererInitParameters) => void;
|
||||
}
|
||||
|
||||
export interface TabPartInitParameters extends GroupPanelPartInitParameters {
|
||||
tabLocation: TabLocation;
|
||||
}
|
||||
|
||||
export interface ITabRenderer
|
||||
extends Optional<Omit<IPanel, 'id'>, RendererMethodOptionalList> {
|
||||
readonly element: HTMLElement;
|
||||
init(parameters: GroupPanelPartInitParameters): void;
|
||||
init(parameters: TabPartInitParameters): void;
|
||||
}
|
||||
|
||||
export interface IContentRenderer
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -11,6 +11,9 @@ import { Classnames } from '../dom';
|
||||
|
||||
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 function toTarget(direction: Direction): Position {
|
||||
|
@ -4,8 +4,7 @@
|
||||
--dv-tab-margin: 0.5rem 0.25rem;
|
||||
--dv-tabs-and-actions-container-height: 44px;
|
||||
|
||||
|
||||
--dv-border-radius
|
||||
--dv-border-radius: 20px;
|
||||
|
||||
.dv-resize-container:has(> .dv-groupview) {
|
||||
border-radius: 8px;
|
||||
@ -27,20 +26,26 @@
|
||||
border: none;
|
||||
}
|
||||
|
||||
.dv-tabs-overflow-container,
|
||||
.dv-tabs-overflow-dropdown-default {
|
||||
border-radius: 8px;
|
||||
height: unset !important;
|
||||
}
|
||||
|
||||
.dv-tab {
|
||||
border-radius: 8px;
|
||||
|
||||
.dv-svg {
|
||||
height: 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-tab {
|
||||
border-radius: 8px;
|
||||
|
||||
.dv-svg {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dv-content-container {
|
||||
|
@ -2,7 +2,6 @@ import type {
|
||||
DockviewApi,
|
||||
DockviewGroupPanel,
|
||||
DockviewPanelApi,
|
||||
GroupPanelPartInitParameters,
|
||||
IContentRenderer,
|
||||
IDockviewPanelHeaderProps,
|
||||
IGroupHeaderProps,
|
||||
@ -12,6 +11,7 @@ import type {
|
||||
IWatermarkRenderer,
|
||||
PanelUpdateEvent,
|
||||
Parameters,
|
||||
TabPartInitParameters,
|
||||
WatermarkRendererInitParameters,
|
||||
} from 'dockview-core';
|
||||
import {
|
||||
@ -121,7 +121,7 @@ export class VueRenderer
|
||||
private _api: DockviewPanelApi | undefined;
|
||||
private _containerApi: DockviewApi | undefined;
|
||||
|
||||
init(parameters: GroupPanelPartInitParameters): void {
|
||||
init(parameters: TabPartInitParameters): void {
|
||||
this._api = parameters.api;
|
||||
this._containerApi = parameters.containerApi;
|
||||
|
||||
@ -129,6 +129,7 @@ export class VueRenderer
|
||||
params: parameters.params,
|
||||
api: parameters.api,
|
||||
containerApi: parameters.containerApi,
|
||||
tabLocation: parameters.tabLocation,
|
||||
};
|
||||
|
||||
this._renderDisposable?.dispose();
|
||||
|
@ -35,6 +35,7 @@ export const DockviewDefaultTab: React.FunctionComponent<
|
||||
onPointerDown,
|
||||
onPointerUp,
|
||||
onPointerLeave,
|
||||
tabLocation,
|
||||
...rest
|
||||
}) => {
|
||||
const title = useTitle(api);
|
||||
@ -96,7 +97,7 @@ export const DockviewDefaultTab: React.FunctionComponent<
|
||||
className="dv-default-tab"
|
||||
>
|
||||
<span className="dv-default-tab-content">{title}</span>
|
||||
{!hideClose && (
|
||||
{!hideClose && tabLocation !== 'headerOverflow' && (
|
||||
<div
|
||||
className="dv-default-tab-action"
|
||||
onPointerDown={onBtnPointerDown}
|
||||
|
@ -3,13 +3,13 @@ import { ReactPart, ReactPortalStore } from '../react';
|
||||
import {
|
||||
PanelUpdateEvent,
|
||||
ITabRenderer,
|
||||
GroupPanelPartInitParameters,
|
||||
IGroupPanelBaseProps,
|
||||
TabPartInitParameters,
|
||||
IDockviewPanelHeaderProps,
|
||||
} from 'dockview-core';
|
||||
|
||||
export class ReactPanelHeaderPart implements ITabRenderer {
|
||||
private readonly _element: HTMLElement;
|
||||
private part?: ReactPart<IGroupPanelBaseProps>;
|
||||
private part?: ReactPart<IDockviewPanelHeaderProps>;
|
||||
|
||||
get element(): HTMLElement {
|
||||
return this._element;
|
||||
@ -17,7 +17,7 @@ export class ReactPanelHeaderPart implements ITabRenderer {
|
||||
|
||||
constructor(
|
||||
public readonly id: string,
|
||||
private readonly component: React.FunctionComponent<IGroupPanelBaseProps>,
|
||||
private readonly component: React.FunctionComponent<IDockviewPanelHeaderProps>,
|
||||
private readonly reactPortalStore: ReactPortalStore
|
||||
) {
|
||||
this._element = document.createElement('div');
|
||||
@ -30,7 +30,7 @@ export class ReactPanelHeaderPart implements ITabRenderer {
|
||||
//noop
|
||||
}
|
||||
|
||||
public init(parameters: GroupPanelPartInitParameters): void {
|
||||
public init(parameters: TabPartInitParameters): void {
|
||||
this.part = new ReactPart(
|
||||
this.element,
|
||||
this.reactPortalStore,
|
||||
@ -39,6 +39,7 @@ export class ReactPanelHeaderPart implements ITabRenderer {
|
||||
params: parameters.params,
|
||||
api: parameters.api,
|
||||
containerApi: parameters.containerApi,
|
||||
tabLocation: parameters.tabLocation,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user