From 04e4ea1a70cc4c3d61c5bdaaf1fef7cde67f5ffa Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Mon, 6 Jan 2025 20:18:18 +0000 Subject: [PATCH] tabs overflow menu --- .../src/dockview/components/popupService.ts | 82 +++++ .../dockview/components/titlebar/tabs.scss | 42 +++ .../src/dockview/components/titlebar/tabs.tsx | 263 ++++++++++++++++ .../components/titlebar/tabsContainer.scss | 61 +--- .../components/titlebar/tabsContainer.ts | 289 +++++------------- .../src/dockview/dockviewComponent.scss | 10 +- .../src/dockview/dockviewComponent.ts | 4 + .../src/dockview/dockviewPanelModel.ts | 6 + 8 files changed, 488 insertions(+), 269 deletions(-) create mode 100644 packages/dockview-core/src/dockview/components/popupService.ts create mode 100644 packages/dockview-core/src/dockview/components/titlebar/tabs.scss create mode 100644 packages/dockview-core/src/dockview/components/titlebar/tabs.tsx diff --git a/packages/dockview-core/src/dockview/components/popupService.ts b/packages/dockview-core/src/dockview/components/popupService.ts new file mode 100644 index 000000000..1fb5a0996 --- /dev/null +++ b/packages/dockview-core/src/dockview/components/popupService.ts @@ -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; + } + } +} diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabs.scss b/packages/dockview-core/src/dockview/components/titlebar/tabs.scss new file mode 100644 index 000000000..01b715404 --- /dev/null +++ b/packages/dockview-core/src/dockview/components/titlebar/tabs.scss @@ -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%; + } + } +} diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabs.tsx b/packages/dockview-core/src/dockview/components/titlebar/tabs.tsx new file mode 100644 index 000000000..0a86dd54b --- /dev/null +++ b/packages/dockview-core/src/dockview/components/titlebar/tabs.tsx @@ -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[] = []; + private selectedIndex = -1; + private _hasOverflow = false; + private _dropdownAnchor: HTMLElement | null = null; + + private readonly _onTabDragStart = new Emitter(); + readonly onTabDragStart: Event = this._onTabDragStart.event; + + private readonly _onDrop = new Emitter(); + readonly onDrop: Event = this._onDrop.event; + + private readonly _onWillShowOverlay = + new Emitter(); + readonly onWillShowOverlay: Event = + 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 = { 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, + 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, + }); + }); + } +} diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.scss b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.scss index fef520e03..14815f8bc 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.scss +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.scss @@ -7,17 +7,17 @@ font-size: var(--dv-tabs-and-actions-container-font-size); &.dv-single-tab.dv-full-width-single-tab { - .dv-tabs-container { - flex-grow: 1; - - .dv-tab { + .dv-tabs-container { flex-grow: 1; - } - } - .dv-void-container { - flex-grow: 0; - } + .dv-tab { + flex-grow: 1; + } + } + + .dv-void-container { + flex-grow: 0; + } } .dv-void-container { @@ -25,47 +25,4 @@ flex-grow: 1; 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%; - } - } - } } diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts index d3bd0568b..41c1e5be8 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts @@ -1,17 +1,14 @@ -import { - IDisposable, - CompositeDisposable, - IValueDisposable, -} from '../../../lifecycle'; +import { IDisposable, CompositeDisposable } from '../../../lifecycle'; import { addDisposableListener, Emitter, Event } from '../../../events'; import { Tab } from '../tab/tab'; import { DockviewGroupPanel } from '../../dockviewGroupPanel'; import { VoidContainer } from './voidContainer'; import { toggleClass } from '../../../dom'; -import { DockviewPanel, IDockviewPanel } from '../../dockviewPanel'; +import { IDockviewPanel } from '../../dockviewPanel'; import { DockviewComponent } from '../../dockviewComponent'; import { WillShowOverlayLocationEvent } from '../../dockviewGroupPanelModel'; import { getPanelData } from '../../../dnd/dataTransfer'; +import { Tabs } from './tabs'; export interface TabDropIndexEvent { readonly event: DragEvent; @@ -56,14 +53,12 @@ export class TabsContainer implements ITabsContainer { private readonly _element: HTMLElement; - private readonly tabContainer: HTMLElement; + private readonly tabs: Tabs; private readonly rightActionsContainer: HTMLElement; private readonly leftActionsContainer: HTMLElement; private readonly preActionsContainer: HTMLElement; private readonly voidContainer: VoidContainer; - private tabs: IValueDisposable[] = []; - private selectedIndex = -1; private rightActions: HTMLElement | undefined; private leftActions: HTMLElement | undefined; private preActions: HTMLElement | undefined; @@ -73,8 +68,9 @@ export class TabsContainer private readonly _onDrop = new Emitter(); readonly onDrop: Event = this._onDrop.event; - private readonly _onTabDragStart = new Emitter(); - readonly onTabDragStart: Event = this._onTabDragStart.event; + get onTabDragStart(): Event { + return this.tabs.onTabDragStart; + } private readonly _onGroupDragStart = new Emitter(); readonly onGroupDragStart: Event = @@ -86,11 +82,11 @@ export class TabsContainer this._onWillShowOverlay.event; get panels(): string[] { - return this.tabs.map((_) => _.value.panel.id); + return this.tabs.panels; } get size(): number { - return this.tabs.length; + return this.tabs.size; } get hidden(): boolean { @@ -102,73 +98,10 @@ export class TabsContainer this.element.style.display = value ? 'none' : ''; } - show(): void { - if (!this.hidden) { - this.element.style.display = ''; - } - } - - hide(): void { - this._element.style.display = 'none'; - } - - setRightActionsElement(element: HTMLElement | undefined): void { - if (this.rightActions === element) { - return; - } - if (this.rightActions) { - this.rightActions.remove(); - this.rightActions = undefined; - } - if (element) { - this.rightActionsContainer.appendChild(element); - this.rightActions = element; - } - } - - setLeftActionsElement(element: HTMLElement | undefined): void { - if (this.leftActions === element) { - return; - } - if (this.leftActions) { - this.leftActions.remove(); - this.leftActions = undefined; - } - if (element) { - this.leftActionsContainer.appendChild(element); - this.leftActions = element; - } - } - - 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; } - public isActive(tab: Tab): boolean { - return ( - this.selectedIndex > -1 && - this.tabs[this.selectedIndex].value === tab - ); - } - - public indexOf(id: string): number { - return this.tabs.findIndex((tab) => tab.value.panel.id === id); - } - constructor( private readonly accessor: DockviewComponent, private readonly group: DockviewGroupPanel @@ -193,13 +126,12 @@ export class TabsContainer this.preActionsContainer = document.createElement('div'); this.preActionsContainer.className = 'dv-pre-actions-container'; - this.tabContainer = document.createElement('div'); - this.tabContainer.className = 'dv-tabs-container'; + this.tabs = new Tabs(group, accessor); this.voidContainer = new VoidContainer(this.accessor, this.group); this._element.appendChild(this.preActionsContainer); - this._element.appendChild(this.tabContainer); + this._element.appendChild(this.tabs.element); this._element.appendChild(this.leftActionsContainer); this._element.appendChild(this.voidContainer.element); this._element.appendChild(this.rightActionsContainer); @@ -207,7 +139,6 @@ export class TabsContainer this.addDisposables( this._onWillShowOverlay, this._onDrop, - this._onTabDragStart, this._onGroupDragStart, this.voidContainer, this.voidContainer.onDragStart((event) => { @@ -219,7 +150,7 @@ export class TabsContainer this.voidContainer.onDrop((event) => { this._onDrop.fire({ event: event.nativeEvent, - index: this.tabs.length, + index: this.tabs.size, }); }), this.voidContainer.onWillShowOverlay((event) => { @@ -259,164 +190,92 @@ export class TabsContainer }); } } - ), - 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 - } - - public delete(id: string): void { - const index = this.tabs.findIndex((tab) => tab.value.panel.id === id); - - const tabToRemove = this.tabs.splice(index, 1)[0]; - - const { value, disposable } = tabToRemove; - - disposable.dispose(); - value.dispose(); - value.element.remove(); - - this.updateClassnames(); + show(): void { + if (!this.hidden) { + this.element.style.display = ''; + } } - public setActivePanel(panel: IDockviewPanel): void { - this.tabs.forEach((tab) => { - const isActivePanel = panel.id === tab.value.panel.id; - tab.value.setActive(isActivePanel); - }); + hide(): void { + this._element.style.display = 'none'; } - public openPanel( - panel: IDockviewPanel, - index: number = this.tabs.length - ): void { - if (this.tabs.find((tab) => tab.value.panel.id === panel.id)) { + setRightActionsElement(element: HTMLElement | undefined): void { + if (this.rightActions === element) { 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 = { value: tab, disposable }; - - this.addTab(value, index); + if (this.rightActions) { + this.rightActions.remove(); + this.rightActions = undefined; + } + if (element) { + this.rightActionsContainer.appendChild(element); + this.rightActions = element; + } } - public closePanel(panel: IDockviewPanel): void { - this.delete(panel.id); + setLeftActionsElement(element: HTMLElement | undefined): void { + if (this.leftActions === element) { + return; + } + if (this.leftActions) { + this.leftActions.remove(); + this.leftActions = undefined; + } + if (element) { + this.leftActionsContainer.appendChild(element); + this.leftActions = element; + } } - public dispose(): void { - super.dispose(); - - for (const { value, disposable } of this.tabs) { - disposable.dispose(); - value.dispose(); + 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; + } + } - this.tabs = []; + isActive(tab: Tab): boolean { + return this.tabs.isActive(tab); } - private addTab( - tab: IValueDisposable, - index: number = this.tabs.length - ): void { - if (index < 0 || index > this.tabs.length) { - throw new Error('invalid location'); - } + indexOf(id: string): number { + return this.tabs.indexOf(id); + } - this.tabContainer.insertBefore( - tab.value.element, - this.tabContainer.children[index] - ); + setActive(_isGroupActive: boolean) { + // noop + } - this.tabs = [ - ...this.tabs.slice(0, index), - tab, - ...this.tabs.slice(index), - ]; + delete(id: string): void { + this.tabs.delete(id); + this.updateClassnames(); + } - if (this.selectedIndex < 0) { - this.selectedIndex = index; - } + setActivePanel(panel: IDockviewPanel): void { + this.tabs.setActivePanel(panel); + } + openPanel(panel: IDockviewPanel, index: number = this.tabs.size): void { + this.tabs.openPanel(panel, index); this.updateClassnames(); } + closePanel(panel: IDockviewPanel): void { + this.delete(panel.id); + } + private updateClassnames(): void { toggleClass(this._element, 'dv-single-tab', this.size === 1); } diff --git a/packages/dockview-core/src/dockview/dockviewComponent.scss b/packages/dockview-core/src/dockview/dockviewComponent.scss index 386bf2a82..b08c0ada0 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.scss +++ b/packages/dockview-core/src/dockview/dockviewComponent.scss @@ -18,7 +18,10 @@ .dv-groupview { &.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 { background-color: var( --dv-activegroup-visiblepanel-tab-background-color @@ -34,7 +37,10 @@ } } &.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 { background-color: var( --dv-inactivegroup-visiblepanel-tab-background-color diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 63915751d..58618a025 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -74,6 +74,7 @@ import { } from '../overlay/overlayRenderContainer'; import { PopoutWindow } from '../popoutWindow'; import { StrictEventsSequencing } from './strictEventsSequencing'; +import { PopupService } from './components/popupService'; const DEFAULT_ROOT_OVERLAY_MODEL: DroptargetOverlayModel = { activationSize: { type: 'pixels', value: 10 }, @@ -256,6 +257,7 @@ export class DockviewComponent private watermark: IWatermarkRenderer | null = null; readonly overlayRenderContainer: OverlayRenderContainer; + readonly popupService: PopupService; private readonly _onWillDragPanel = new Emitter(); readonly onWillDragPanel: Event = this._onWillDragPanel.event; @@ -381,6 +383,8 @@ export class DockviewComponent className: options.className, }); + this.popupService = new PopupService(this.element); + this.overlayRenderContainer = new OverlayRenderContainer( this.gridview.element, this diff --git a/packages/dockview-core/src/dockview/dockviewPanelModel.ts b/packages/dockview-core/src/dockview/dockviewPanelModel.ts index 950b4d577..777717bad 100644 --- a/packages/dockview-core/src/dockview/dockviewPanelModel.ts +++ b/packages/dockview-core/src/dockview/dockviewPanelModel.ts @@ -14,6 +14,7 @@ export interface IDockviewPanelModel extends IDisposable { 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; @@ -42,6 +43,11 @@ export class DockviewPanelModel implements IDockviewPanelModel { this._tab = this.createTabComponent(this.id, tabComponent); } + get newTab() { + const cmp = this.createTabComponent(this.id, this.tabComponent); + return cmp; + } + init(params: GroupPanelPartInitParameters): void { this.content.init(params); this.tab.init(params);