mirror of
https://github.com/mathuo/dockview
synced 2025-02-02 06:25:44 +00:00
tabs overflow menu
This commit is contained in:
parent
148a05a201
commit
04e4ea1a70
@ -0,0 +1,82 @@
|
|||||||
|
import { addDisposableWindowListener } from '../../events';
|
||||||
|
import {
|
||||||
|
CompositeDisposable,
|
||||||
|
Disposable,
|
||||||
|
MutableDisposable,
|
||||||
|
} from '../../lifecycle';
|
||||||
|
|
||||||
|
export class PopupService extends CompositeDisposable {
|
||||||
|
private readonly _element: HTMLElement;
|
||||||
|
private _active: HTMLElement | null = null;
|
||||||
|
private _activeDisposable = new MutableDisposable();
|
||||||
|
|
||||||
|
constructor(private readonly root: HTMLElement) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._element = document.createElement('div');
|
||||||
|
this._element.className = 'dv-popover-anchor';
|
||||||
|
this._element.style.position = 'relative';
|
||||||
|
|
||||||
|
this.root.prepend(this._element);
|
||||||
|
|
||||||
|
this.addDisposables(
|
||||||
|
Disposable.from(() => {
|
||||||
|
this.close();
|
||||||
|
}),
|
||||||
|
this._activeDisposable
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
openPopover(
|
||||||
|
element: HTMLElement,
|
||||||
|
position: { x: number; y: number }
|
||||||
|
): void {
|
||||||
|
this.close();
|
||||||
|
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.style.position = 'absolute';
|
||||||
|
wrapper.style.zIndex = '99';
|
||||||
|
wrapper.appendChild(element);
|
||||||
|
|
||||||
|
const anchorBox = this._element.getBoundingClientRect();
|
||||||
|
const offsetX = anchorBox.left;
|
||||||
|
const offsetY = anchorBox.top;
|
||||||
|
|
||||||
|
wrapper.style.top = `${position.y - offsetY}px`;
|
||||||
|
wrapper.style.left = `${position.x - offsetX}px`;
|
||||||
|
|
||||||
|
this._element.appendChild(wrapper);
|
||||||
|
|
||||||
|
this._active = wrapper;
|
||||||
|
|
||||||
|
this._activeDisposable.value = new CompositeDisposable(
|
||||||
|
addDisposableWindowListener(window, 'pointerdown', (event) => {
|
||||||
|
const target = event.target;
|
||||||
|
|
||||||
|
if (!(target instanceof HTMLElement)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let el: HTMLElement | null = target;
|
||||||
|
|
||||||
|
while (el && el !== wrapper) {
|
||||||
|
el = el?.parentElement ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (el) {
|
||||||
|
return; // clicked within popover
|
||||||
|
}
|
||||||
|
|
||||||
|
this.close();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
close(): void {
|
||||||
|
if (this._active) {
|
||||||
|
this._active.remove();
|
||||||
|
this._activeDisposable.dispose();
|
||||||
|
this._active = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
.dv-tabs-container {
|
||||||
|
display: flex;
|
||||||
|
overflow-x: overlay;
|
||||||
|
overflow-y: 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;
|
||||||
|
min-width: 75px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
&: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%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
263
packages/dockview-core/src/dockview/components/titlebar/tabs.tsx
Normal file
263
packages/dockview-core/src/dockview/components/titlebar/tabs.tsx
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
import { getPanelData } from '../../../dnd/dataTransfer';
|
||||||
|
import { OverflowObserver } from '../../../dom';
|
||||||
|
import { addDisposableListener, Emitter, Event } from '../../../events';
|
||||||
|
import {
|
||||||
|
CompositeDisposable,
|
||||||
|
Disposable,
|
||||||
|
IValueDisposable,
|
||||||
|
} from '../../../lifecycle';
|
||||||
|
import { DockviewComponent } from '../../dockviewComponent';
|
||||||
|
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
|
||||||
|
import { WillShowOverlayLocationEvent } from '../../dockviewGroupPanelModel';
|
||||||
|
import { DockviewPanel, IDockviewPanel } from '../../dockviewPanel';
|
||||||
|
import { Tab } from '../tab/tab';
|
||||||
|
import { TabDragEvent, TabDropIndexEvent } from './tabsContainer';
|
||||||
|
|
||||||
|
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 _onTabDragStart = new Emitter<TabDragEvent>();
|
||||||
|
readonly onTabDragStart: Event<TabDragEvent> = this._onTabDragStart.event;
|
||||||
|
|
||||||
|
private readonly _onDrop = new Emitter<TabDropIndexEvent>();
|
||||||
|
readonly onDrop: Event<TabDropIndexEvent> = this._onDrop.event;
|
||||||
|
|
||||||
|
private readonly _onWillShowOverlay =
|
||||||
|
new Emitter<WillShowOverlayLocationEvent>();
|
||||||
|
readonly onWillShowOverlay: Event<WillShowOverlayLocationEvent> =
|
||||||
|
this._onWillShowOverlay.event;
|
||||||
|
|
||||||
|
get element(): HTMLElement {
|
||||||
|
return this._element;
|
||||||
|
}
|
||||||
|
|
||||||
|
get panels(): string[] {
|
||||||
|
return this.tabs.map((_) => _.value.panel.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
get size(): number {
|
||||||
|
return this.tabs.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly group: DockviewGroupPanel,
|
||||||
|
private readonly accessor: DockviewComponent
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._element = document.createElement('div');
|
||||||
|
this._element.className = 'dv-tabs-panel';
|
||||||
|
this._element.style.display = 'flex';
|
||||||
|
this._element.style.overflow = 'auto';
|
||||||
|
this._tabsList = document.createElement('div');
|
||||||
|
this._tabsList.className = 'dv-tabs-container';
|
||||||
|
this._element.appendChild(this._tabsList);
|
||||||
|
|
||||||
|
const observer = new OverflowObserver(this._tabsList);
|
||||||
|
|
||||||
|
this.addDisposables(
|
||||||
|
observer,
|
||||||
|
observer.onDidChange((event) => {
|
||||||
|
const hasOverflow = event.hasScrollX || event.hasScrollY;
|
||||||
|
if (this._hasOverflow !== hasOverflow) {
|
||||||
|
this.toggleDropdown(hasOverflow);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
addDisposableListener(this.element, 'pointerdown', (event) => {
|
||||||
|
if (event.defaultPrevented) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLeftClick = event.button === 0;
|
||||||
|
|
||||||
|
if (isLeftClick) {
|
||||||
|
this.accessor.doSetGroupActive(this.group);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
Disposable.from(() => {
|
||||||
|
for (const { value, disposable } of this.tabs) {
|
||||||
|
disposable.dispose();
|
||||||
|
value.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tabs = [];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
indexOf(id: string): number {
|
||||||
|
return this.tabs.findIndex((tab) => tab.value.panel.id === id);
|
||||||
|
}
|
||||||
|
|
||||||
|
isActive(tab: Tab): boolean {
|
||||||
|
return (
|
||||||
|
this.selectedIndex > -1 &&
|
||||||
|
this.tabs[this.selectedIndex].value === tab
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setActivePanel(panel: IDockviewPanel): void {
|
||||||
|
this.tabs.forEach((tab) => {
|
||||||
|
const isActivePanel = panel.id === tab.value.panel.id;
|
||||||
|
tab.value.setActive(isActivePanel);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
openPanel(panel: IDockviewPanel, index: number = this.tabs.length): void {
|
||||||
|
if (this.tabs.find((tab) => tab.value.panel.id === panel.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const tab = new Tab(panel, this.accessor, this.group);
|
||||||
|
tab.setContent(panel.view.tab);
|
||||||
|
|
||||||
|
const disposable = new CompositeDisposable(
|
||||||
|
tab.onDragStart((event) => {
|
||||||
|
this._onTabDragStart.fire({ nativeEvent: event, panel });
|
||||||
|
}),
|
||||||
|
tab.onChanged((event) => {
|
||||||
|
const isFloatingGroupsEnabled =
|
||||||
|
!this.accessor.options.disableFloatingGroups;
|
||||||
|
|
||||||
|
const isFloatingWithOnePanel =
|
||||||
|
this.group.api.location.type === 'floating' &&
|
||||||
|
this.size === 1;
|
||||||
|
|
||||||
|
if (
|
||||||
|
isFloatingGroupsEnabled &&
|
||||||
|
!isFloatingWithOnePanel &&
|
||||||
|
event.shiftKey
|
||||||
|
) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const panel = this.accessor.getGroupPanel(tab.panel.id);
|
||||||
|
|
||||||
|
const { top, left } = tab.element.getBoundingClientRect();
|
||||||
|
const { top: rootTop, left: rootLeft } =
|
||||||
|
this.accessor.element.getBoundingClientRect();
|
||||||
|
|
||||||
|
this.accessor.addFloatingGroup(panel as DockviewPanel, {
|
||||||
|
x: left - rootLeft,
|
||||||
|
y: top - rootTop,
|
||||||
|
inDragMode: true,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLeftClick = event.button === 0;
|
||||||
|
|
||||||
|
if (!isLeftClick || event.defaultPrevented) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.group.activePanel !== panel) {
|
||||||
|
this.group.model.openPanel(panel);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
tab.onDrop((event) => {
|
||||||
|
this._onDrop.fire({
|
||||||
|
event: event.nativeEvent,
|
||||||
|
index: this.tabs.findIndex((x) => x.value === tab),
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
tab.onWillShowOverlay((event) => {
|
||||||
|
this._onWillShowOverlay.fire(
|
||||||
|
new WillShowOverlayLocationEvent(event, {
|
||||||
|
kind: 'tab',
|
||||||
|
panel: this.group.activePanel,
|
||||||
|
api: this.accessor.api,
|
||||||
|
group: this.group,
|
||||||
|
getData: getPanelData,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const value: IValueDisposable<Tab> = { value: tab, disposable };
|
||||||
|
|
||||||
|
this.addTab(value, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(id: string): void {
|
||||||
|
const index = this.indexOf(id);
|
||||||
|
const tabToRemove = this.tabs.splice(index, 1)[0];
|
||||||
|
|
||||||
|
const { value, disposable } = tabToRemove;
|
||||||
|
|
||||||
|
disposable.dispose();
|
||||||
|
value.dispose();
|
||||||
|
value.element.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
private addTab(
|
||||||
|
tab: IValueDisposable<Tab>,
|
||||||
|
index: number = this.tabs.length
|
||||||
|
): void {
|
||||||
|
if (index < 0 || index > this.tabs.length) {
|
||||||
|
throw new Error('invalid location');
|
||||||
|
}
|
||||||
|
|
||||||
|
this._tabsList.insertBefore(
|
||||||
|
tab.value.element,
|
||||||
|
this._tabsList.children[index]
|
||||||
|
);
|
||||||
|
|
||||||
|
this.tabs = [
|
||||||
|
...this.tabs.slice(0, index),
|
||||||
|
tab,
|
||||||
|
...this.tabs.slice(index),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (this.selectedIndex < 0) {
|
||||||
|
this.selectedIndex = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private toggleDropdown(show: boolean): void {
|
||||||
|
this._hasOverflow = show;
|
||||||
|
if (this._dropdownAnchor) {
|
||||||
|
this._dropdownAnchor.remove();
|
||||||
|
this._dropdownAnchor = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!show) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._dropdownAnchor = document.createElement('div');
|
||||||
|
this._dropdownAnchor.style.width = '10px';
|
||||||
|
this._dropdownAnchor.style.height = '100%';
|
||||||
|
this._dropdownAnchor.style.flexShrink = '0';
|
||||||
|
this._dropdownAnchor.style.backgroundColor = 'red';
|
||||||
|
|
||||||
|
this.element.appendChild(this._dropdownAnchor);
|
||||||
|
|
||||||
|
addDisposableListener(this._dropdownAnchor, 'click', (event) => {
|
||||||
|
const el = document.createElement('div');
|
||||||
|
el.style.width = '200px';
|
||||||
|
el.style.maxHeight = '600px';
|
||||||
|
el.style.overflow = 'auto';
|
||||||
|
el.style.backgroundColor = 'lightgreen';
|
||||||
|
|
||||||
|
this.tabs.map((tab) => {
|
||||||
|
const tab2 = new Tab(
|
||||||
|
tab.value.panel,
|
||||||
|
this.accessor,
|
||||||
|
this.group
|
||||||
|
);
|
||||||
|
tab2.setContent(tab.value.panel.view.newTab);
|
||||||
|
el.appendChild(tab2.element);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.accessor.popupService.openPopover(el, {
|
||||||
|
x: event.clientX,
|
||||||
|
y: event.clientY,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -25,47 +25,4 @@
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dv-tabs-container {
|
|
||||||
display: flex;
|
|
||||||
overflow-x: overlay;
|
|
||||||
overflow-y: 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;
|
|
||||||
min-width: 75px;
|
|
||||||
cursor: pointer;
|
|
||||||
position: relative;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
&: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%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
import {
|
import { IDisposable, CompositeDisposable } from '../../../lifecycle';
|
||||||
IDisposable,
|
|
||||||
CompositeDisposable,
|
|
||||||
IValueDisposable,
|
|
||||||
} from '../../../lifecycle';
|
|
||||||
import { addDisposableListener, Emitter, Event } from '../../../events';
|
import { addDisposableListener, Emitter, Event } from '../../../events';
|
||||||
import { Tab } from '../tab/tab';
|
import { Tab } from '../tab/tab';
|
||||||
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
|
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
|
||||||
import { VoidContainer } from './voidContainer';
|
import { VoidContainer } from './voidContainer';
|
||||||
import { toggleClass } from '../../../dom';
|
import { toggleClass } from '../../../dom';
|
||||||
import { DockviewPanel, IDockviewPanel } from '../../dockviewPanel';
|
import { IDockviewPanel } from '../../dockviewPanel';
|
||||||
import { DockviewComponent } from '../../dockviewComponent';
|
import { DockviewComponent } from '../../dockviewComponent';
|
||||||
import { WillShowOverlayLocationEvent } from '../../dockviewGroupPanelModel';
|
import { WillShowOverlayLocationEvent } from '../../dockviewGroupPanelModel';
|
||||||
import { getPanelData } from '../../../dnd/dataTransfer';
|
import { getPanelData } from '../../../dnd/dataTransfer';
|
||||||
|
import { Tabs } from './tabs';
|
||||||
|
|
||||||
export interface TabDropIndexEvent {
|
export interface TabDropIndexEvent {
|
||||||
readonly event: DragEvent;
|
readonly event: DragEvent;
|
||||||
@ -56,14 +53,12 @@ export class TabsContainer
|
|||||||
implements ITabsContainer
|
implements ITabsContainer
|
||||||
{
|
{
|
||||||
private readonly _element: HTMLElement;
|
private readonly _element: HTMLElement;
|
||||||
private readonly tabContainer: HTMLElement;
|
private readonly tabs: Tabs;
|
||||||
private readonly rightActionsContainer: HTMLElement;
|
private readonly rightActionsContainer: HTMLElement;
|
||||||
private readonly leftActionsContainer: HTMLElement;
|
private readonly leftActionsContainer: HTMLElement;
|
||||||
private readonly preActionsContainer: HTMLElement;
|
private readonly preActionsContainer: HTMLElement;
|
||||||
private readonly voidContainer: VoidContainer;
|
private readonly voidContainer: VoidContainer;
|
||||||
|
|
||||||
private tabs: IValueDisposable<Tab>[] = [];
|
|
||||||
private selectedIndex = -1;
|
|
||||||
private rightActions: HTMLElement | undefined;
|
private rightActions: HTMLElement | undefined;
|
||||||
private leftActions: HTMLElement | undefined;
|
private leftActions: HTMLElement | undefined;
|
||||||
private preActions: HTMLElement | undefined;
|
private preActions: HTMLElement | undefined;
|
||||||
@ -73,8 +68,9 @@ export class TabsContainer
|
|||||||
private readonly _onDrop = new Emitter<TabDropIndexEvent>();
|
private readonly _onDrop = new Emitter<TabDropIndexEvent>();
|
||||||
readonly onDrop: Event<TabDropIndexEvent> = this._onDrop.event;
|
readonly onDrop: Event<TabDropIndexEvent> = this._onDrop.event;
|
||||||
|
|
||||||
private readonly _onTabDragStart = new Emitter<TabDragEvent>();
|
get onTabDragStart(): Event<TabDragEvent> {
|
||||||
readonly onTabDragStart: Event<TabDragEvent> = this._onTabDragStart.event;
|
return this.tabs.onTabDragStart;
|
||||||
|
}
|
||||||
|
|
||||||
private readonly _onGroupDragStart = new Emitter<GroupDragEvent>();
|
private readonly _onGroupDragStart = new Emitter<GroupDragEvent>();
|
||||||
readonly onGroupDragStart: Event<GroupDragEvent> =
|
readonly onGroupDragStart: Event<GroupDragEvent> =
|
||||||
@ -86,11 +82,11 @@ export class TabsContainer
|
|||||||
this._onWillShowOverlay.event;
|
this._onWillShowOverlay.event;
|
||||||
|
|
||||||
get panels(): string[] {
|
get panels(): string[] {
|
||||||
return this.tabs.map((_) => _.value.panel.id);
|
return this.tabs.panels;
|
||||||
}
|
}
|
||||||
|
|
||||||
get size(): number {
|
get size(): number {
|
||||||
return this.tabs.length;
|
return this.tabs.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
get hidden(): boolean {
|
get hidden(): boolean {
|
||||||
@ -102,6 +98,102 @@ export class TabsContainer
|
|||||||
this.element.style.display = value ? 'none' : '';
|
this.element.style.display = value ? 'none' : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get element(): HTMLElement {
|
||||||
|
return this._element;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly accessor: DockviewComponent,
|
||||||
|
private readonly group: DockviewGroupPanel
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._element = document.createElement('div');
|
||||||
|
this._element.className = 'dv-tabs-and-actions-container';
|
||||||
|
|
||||||
|
toggleClass(
|
||||||
|
this._element,
|
||||||
|
'dv-full-width-single-tab',
|
||||||
|
this.accessor.options.singleTabMode === 'fullwidth'
|
||||||
|
);
|
||||||
|
|
||||||
|
this.rightActionsContainer = document.createElement('div');
|
||||||
|
this.rightActionsContainer.className = 'dv-right-actions-container';
|
||||||
|
|
||||||
|
this.leftActionsContainer = document.createElement('div');
|
||||||
|
this.leftActionsContainer.className = 'dv-left-actions-container';
|
||||||
|
|
||||||
|
this.preActionsContainer = document.createElement('div');
|
||||||
|
this.preActionsContainer.className = 'dv-pre-actions-container';
|
||||||
|
|
||||||
|
this.tabs = new Tabs(group, accessor);
|
||||||
|
|
||||||
|
this.voidContainer = new VoidContainer(this.accessor, this.group);
|
||||||
|
|
||||||
|
this._element.appendChild(this.preActionsContainer);
|
||||||
|
this._element.appendChild(this.tabs.element);
|
||||||
|
this._element.appendChild(this.leftActionsContainer);
|
||||||
|
this._element.appendChild(this.voidContainer.element);
|
||||||
|
this._element.appendChild(this.rightActionsContainer);
|
||||||
|
|
||||||
|
this.addDisposables(
|
||||||
|
this._onWillShowOverlay,
|
||||||
|
this._onDrop,
|
||||||
|
this._onGroupDragStart,
|
||||||
|
this.voidContainer,
|
||||||
|
this.voidContainer.onDragStart((event) => {
|
||||||
|
this._onGroupDragStart.fire({
|
||||||
|
nativeEvent: event,
|
||||||
|
group: this.group,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
this.voidContainer.onDrop((event) => {
|
||||||
|
this._onDrop.fire({
|
||||||
|
event: event.nativeEvent,
|
||||||
|
index: this.tabs.size,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
this.voidContainer.onWillShowOverlay((event) => {
|
||||||
|
this._onWillShowOverlay.fire(
|
||||||
|
new WillShowOverlayLocationEvent(event, {
|
||||||
|
kind: 'header_space',
|
||||||
|
panel: this.group.activePanel,
|
||||||
|
api: this.accessor.api,
|
||||||
|
group: this.group,
|
||||||
|
getData: getPanelData,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
addDisposableListener(
|
||||||
|
this.voidContainer.element,
|
||||||
|
'pointerdown',
|
||||||
|
(event) => {
|
||||||
|
const isFloatingGroupsEnabled =
|
||||||
|
!this.accessor.options.disableFloatingGroups;
|
||||||
|
|
||||||
|
if (
|
||||||
|
isFloatingGroupsEnabled &&
|
||||||
|
event.shiftKey &&
|
||||||
|
this.group.api.location.type !== 'floating'
|
||||||
|
) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const { top, left } =
|
||||||
|
this.element.getBoundingClientRect();
|
||||||
|
const { top: rootTop, left: rootLeft } =
|
||||||
|
this.accessor.element.getBoundingClientRect();
|
||||||
|
|
||||||
|
this.accessor.addFloatingGroup(this.group, {
|
||||||
|
x: left - rootLeft + 20,
|
||||||
|
y: top - rootTop + 20,
|
||||||
|
inDragMode: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
show(): void {
|
show(): void {
|
||||||
if (!this.hidden) {
|
if (!this.hidden) {
|
||||||
this.element.style.display = '';
|
this.element.style.display = '';
|
||||||
@ -154,269 +246,36 @@ export class TabsContainer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get element(): HTMLElement {
|
isActive(tab: Tab): boolean {
|
||||||
return this._element;
|
return this.tabs.isActive(tab);
|
||||||
}
|
}
|
||||||
|
|
||||||
public isActive(tab: Tab): boolean {
|
indexOf(id: string): number {
|
||||||
return (
|
return this.tabs.indexOf(id);
|
||||||
this.selectedIndex > -1 &&
|
|
||||||
this.tabs[this.selectedIndex].value === tab
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public indexOf(id: string): number {
|
setActive(_isGroupActive: boolean) {
|
||||||
return this.tabs.findIndex((tab) => tab.value.panel.id === id);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private readonly accessor: DockviewComponent,
|
|
||||||
private readonly group: DockviewGroupPanel
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this._element = document.createElement('div');
|
|
||||||
this._element.className = 'dv-tabs-and-actions-container';
|
|
||||||
|
|
||||||
toggleClass(
|
|
||||||
this._element,
|
|
||||||
'dv-full-width-single-tab',
|
|
||||||
this.accessor.options.singleTabMode === 'fullwidth'
|
|
||||||
);
|
|
||||||
|
|
||||||
this.rightActionsContainer = document.createElement('div');
|
|
||||||
this.rightActionsContainer.className = 'dv-right-actions-container';
|
|
||||||
|
|
||||||
this.leftActionsContainer = document.createElement('div');
|
|
||||||
this.leftActionsContainer.className = 'dv-left-actions-container';
|
|
||||||
|
|
||||||
this.preActionsContainer = document.createElement('div');
|
|
||||||
this.preActionsContainer.className = 'dv-pre-actions-container';
|
|
||||||
|
|
||||||
this.tabContainer = document.createElement('div');
|
|
||||||
this.tabContainer.className = 'dv-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);
|
|
||||||
this._element.appendChild(this.rightActionsContainer);
|
|
||||||
|
|
||||||
this.addDisposables(
|
|
||||||
this._onWillShowOverlay,
|
|
||||||
this._onDrop,
|
|
||||||
this._onTabDragStart,
|
|
||||||
this._onGroupDragStart,
|
|
||||||
this.voidContainer,
|
|
||||||
this.voidContainer.onDragStart((event) => {
|
|
||||||
this._onGroupDragStart.fire({
|
|
||||||
nativeEvent: event,
|
|
||||||
group: this.group,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
this.voidContainer.onDrop((event) => {
|
|
||||||
this._onDrop.fire({
|
|
||||||
event: event.nativeEvent,
|
|
||||||
index: this.tabs.length,
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
this.voidContainer.onWillShowOverlay((event) => {
|
|
||||||
this._onWillShowOverlay.fire(
|
|
||||||
new WillShowOverlayLocationEvent(event, {
|
|
||||||
kind: 'header_space',
|
|
||||||
panel: this.group.activePanel,
|
|
||||||
api: this.accessor.api,
|
|
||||||
group: this.group,
|
|
||||||
getData: getPanelData,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
addDisposableListener(
|
|
||||||
this.voidContainer.element,
|
|
||||||
'pointerdown',
|
|
||||||
(event) => {
|
|
||||||
const isFloatingGroupsEnabled =
|
|
||||||
!this.accessor.options.disableFloatingGroups;
|
|
||||||
|
|
||||||
if (
|
|
||||||
isFloatingGroupsEnabled &&
|
|
||||||
event.shiftKey &&
|
|
||||||
this.group.api.location.type !== 'floating'
|
|
||||||
) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
const { top, left } =
|
|
||||||
this.element.getBoundingClientRect();
|
|
||||||
const { top: rootTop, left: rootLeft } =
|
|
||||||
this.accessor.element.getBoundingClientRect();
|
|
||||||
|
|
||||||
this.accessor.addFloatingGroup(this.group, {
|
|
||||||
x: left - rootLeft + 20,
|
|
||||||
y: top - rootTop + 20,
|
|
||||||
inDragMode: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
|
||||||
addDisposableListener(this.tabContainer, 'pointerdown', (event) => {
|
|
||||||
if (event.defaultPrevented) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isLeftClick = event.button === 0;
|
|
||||||
|
|
||||||
if (isLeftClick) {
|
|
||||||
this.accessor.doSetGroupActive(this.group);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public setActive(_isGroupActive: boolean) {
|
|
||||||
// noop
|
// noop
|
||||||
}
|
}
|
||||||
|
|
||||||
public delete(id: string): void {
|
delete(id: string): void {
|
||||||
const index = this.tabs.findIndex((tab) => tab.value.panel.id === id);
|
this.tabs.delete(id);
|
||||||
|
|
||||||
const tabToRemove = this.tabs.splice(index, 1)[0];
|
|
||||||
|
|
||||||
const { value, disposable } = tabToRemove;
|
|
||||||
|
|
||||||
disposable.dispose();
|
|
||||||
value.dispose();
|
|
||||||
value.element.remove();
|
|
||||||
|
|
||||||
this.updateClassnames();
|
this.updateClassnames();
|
||||||
}
|
}
|
||||||
|
|
||||||
public setActivePanel(panel: IDockviewPanel): void {
|
setActivePanel(panel: IDockviewPanel): void {
|
||||||
this.tabs.forEach((tab) => {
|
this.tabs.setActivePanel(panel);
|
||||||
const isActivePanel = panel.id === tab.value.panel.id;
|
|
||||||
tab.value.setActive(isActivePanel);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public openPanel(
|
openPanel(panel: IDockviewPanel, index: number = this.tabs.size): void {
|
||||||
panel: IDockviewPanel,
|
this.tabs.openPanel(panel, index);
|
||||||
index: number = this.tabs.length
|
this.updateClassnames();
|
||||||
): void {
|
|
||||||
if (this.tabs.find((tab) => tab.value.panel.id === panel.id)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const tab = new Tab(panel, this.accessor, this.group);
|
|
||||||
tab.setContent(panel.view.tab);
|
|
||||||
|
|
||||||
const disposable = new CompositeDisposable(
|
|
||||||
tab.onDragStart((event) => {
|
|
||||||
this._onTabDragStart.fire({ nativeEvent: event, panel });
|
|
||||||
}),
|
|
||||||
tab.onChanged((event) => {
|
|
||||||
const isFloatingGroupsEnabled =
|
|
||||||
!this.accessor.options.disableFloatingGroups;
|
|
||||||
|
|
||||||
const isFloatingWithOnePanel =
|
|
||||||
this.group.api.location.type === 'floating' &&
|
|
||||||
this.size === 1;
|
|
||||||
|
|
||||||
if (
|
|
||||||
isFloatingGroupsEnabled &&
|
|
||||||
!isFloatingWithOnePanel &&
|
|
||||||
event.shiftKey
|
|
||||||
) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
const panel = this.accessor.getGroupPanel(tab.panel.id);
|
|
||||||
|
|
||||||
const { top, left } = tab.element.getBoundingClientRect();
|
|
||||||
const { top: rootTop, left: rootLeft } =
|
|
||||||
this.accessor.element.getBoundingClientRect();
|
|
||||||
|
|
||||||
this.accessor.addFloatingGroup(panel as DockviewPanel, {
|
|
||||||
x: left - rootLeft,
|
|
||||||
y: top - rootTop,
|
|
||||||
inDragMode: true,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const isLeftClick = event.button === 0;
|
closePanel(panel: IDockviewPanel): void {
|
||||||
|
|
||||||
if (!isLeftClick || event.defaultPrevented) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.group.activePanel !== panel) {
|
|
||||||
this.group.model.openPanel(panel);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
tab.onDrop((event) => {
|
|
||||||
this._onDrop.fire({
|
|
||||||
event: event.nativeEvent,
|
|
||||||
index: this.tabs.findIndex((x) => x.value === tab),
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
tab.onWillShowOverlay((event) => {
|
|
||||||
this._onWillShowOverlay.fire(
|
|
||||||
new WillShowOverlayLocationEvent(event, {
|
|
||||||
kind: 'tab',
|
|
||||||
panel: this.group.activePanel,
|
|
||||||
api: this.accessor.api,
|
|
||||||
group: this.group,
|
|
||||||
getData: getPanelData,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const value: IValueDisposable<Tab> = { value: tab, disposable };
|
|
||||||
|
|
||||||
this.addTab(value, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
public closePanel(panel: IDockviewPanel): void {
|
|
||||||
this.delete(panel.id);
|
this.delete(panel.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public dispose(): void {
|
|
||||||
super.dispose();
|
|
||||||
|
|
||||||
for (const { value, disposable } of this.tabs) {
|
|
||||||
disposable.dispose();
|
|
||||||
value.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.tabs = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
private addTab(
|
|
||||||
tab: IValueDisposable<Tab>,
|
|
||||||
index: number = this.tabs.length
|
|
||||||
): void {
|
|
||||||
if (index < 0 || index > this.tabs.length) {
|
|
||||||
throw new Error('invalid location');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.tabContainer.insertBefore(
|
|
||||||
tab.value.element,
|
|
||||||
this.tabContainer.children[index]
|
|
||||||
);
|
|
||||||
|
|
||||||
this.tabs = [
|
|
||||||
...this.tabs.slice(0, index),
|
|
||||||
tab,
|
|
||||||
...this.tabs.slice(index),
|
|
||||||
];
|
|
||||||
|
|
||||||
if (this.selectedIndex < 0) {
|
|
||||||
this.selectedIndex = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateClassnames();
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateClassnames(): void {
|
private updateClassnames(): void {
|
||||||
toggleClass(this._element, 'dv-single-tab', this.size === 1);
|
toggleClass(this._element, 'dv-single-tab', this.size === 1);
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,10 @@
|
|||||||
|
|
||||||
.dv-groupview {
|
.dv-groupview {
|
||||||
&.dv-active-group {
|
&.dv-active-group {
|
||||||
> .dv-tabs-and-actions-container > .dv-tabs-container > .dv-tab {
|
> .dv-tabs-and-actions-container
|
||||||
|
> .dv-tabs-panel
|
||||||
|
> .dv-tabs-container
|
||||||
|
> .dv-tab {
|
||||||
&.dv-active-tab {
|
&.dv-active-tab {
|
||||||
background-color: var(
|
background-color: var(
|
||||||
--dv-activegroup-visiblepanel-tab-background-color
|
--dv-activegroup-visiblepanel-tab-background-color
|
||||||
@ -34,7 +37,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.dv-inactive-group {
|
&.dv-inactive-group {
|
||||||
> .dv-tabs-and-actions-container > .dv-tabs-container > .dv-tab {
|
> .dv-tabs-and-actions-container
|
||||||
|
> .dv-tabs-panel
|
||||||
|
> .dv-tabs-container
|
||||||
|
> .dv-tab {
|
||||||
&.dv-active-tab {
|
&.dv-active-tab {
|
||||||
background-color: var(
|
background-color: var(
|
||||||
--dv-inactivegroup-visiblepanel-tab-background-color
|
--dv-inactivegroup-visiblepanel-tab-background-color
|
||||||
|
@ -74,6 +74,7 @@ import {
|
|||||||
} from '../overlay/overlayRenderContainer';
|
} from '../overlay/overlayRenderContainer';
|
||||||
import { PopoutWindow } from '../popoutWindow';
|
import { PopoutWindow } from '../popoutWindow';
|
||||||
import { StrictEventsSequencing } from './strictEventsSequencing';
|
import { StrictEventsSequencing } from './strictEventsSequencing';
|
||||||
|
import { PopupService } from './components/popupService';
|
||||||
|
|
||||||
const DEFAULT_ROOT_OVERLAY_MODEL: DroptargetOverlayModel = {
|
const DEFAULT_ROOT_OVERLAY_MODEL: DroptargetOverlayModel = {
|
||||||
activationSize: { type: 'pixels', value: 10 },
|
activationSize: { type: 'pixels', value: 10 },
|
||||||
@ -256,6 +257,7 @@ export class DockviewComponent
|
|||||||
private watermark: IWatermarkRenderer | null = null;
|
private watermark: IWatermarkRenderer | null = null;
|
||||||
|
|
||||||
readonly overlayRenderContainer: OverlayRenderContainer;
|
readonly overlayRenderContainer: OverlayRenderContainer;
|
||||||
|
readonly popupService: PopupService;
|
||||||
|
|
||||||
private readonly _onWillDragPanel = new Emitter<TabDragEvent>();
|
private readonly _onWillDragPanel = new Emitter<TabDragEvent>();
|
||||||
readonly onWillDragPanel: Event<TabDragEvent> = this._onWillDragPanel.event;
|
readonly onWillDragPanel: Event<TabDragEvent> = this._onWillDragPanel.event;
|
||||||
@ -381,6 +383,8 @@ export class DockviewComponent
|
|||||||
className: options.className,
|
className: options.className,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.popupService = new PopupService(this.element);
|
||||||
|
|
||||||
this.overlayRenderContainer = new OverlayRenderContainer(
|
this.overlayRenderContainer = new OverlayRenderContainer(
|
||||||
this.gridview.element,
|
this.gridview.element,
|
||||||
this
|
this
|
||||||
|
@ -14,6 +14,7 @@ export interface IDockviewPanelModel extends IDisposable {
|
|||||||
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;
|
||||||
@ -42,6 +43,11 @@ export class DockviewPanelModel implements IDockviewPanelModel {
|
|||||||
this._tab = this.createTabComponent(this.id, tabComponent);
|
this._tab = this.createTabComponent(this.id, tabComponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get newTab() {
|
||||||
|
const cmp = this.createTabComponent(this.id, this.tabComponent);
|
||||||
|
return cmp;
|
||||||
|
}
|
||||||
|
|
||||||
init(params: GroupPanelPartInitParameters): void {
|
init(params: GroupPanelPartInitParameters): void {
|
||||||
this.content.init(params);
|
this.content.init(params);
|
||||||
this.tab.init(params);
|
this.tab.init(params);
|
||||||
|
Loading…
Reference in New Issue
Block a user