From 586400019f6497317124aa1a2c1daaf633bce3e6 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sat, 27 Jan 2024 20:26:00 +0000 Subject: [PATCH 1/3] feat: align focus/active methods --- .../dockview-core/src/api/dockviewPanelApi.ts | 2 ++ packages/dockview-core/src/api/panelApi.ts | 20 +++++++++++++------ .../components/titlebar/tabsContainer.ts | 4 +--- .../src/dockview/dockviewComponent.ts | 3 +-- .../src/dockview/dockviewGroupPanelModel.ts | 12 +++++------ .../src/dockview/dockviewPanel.ts | 20 ++++++++++++++++++- packages/dockview-core/src/events.ts | 11 ++++++++++ .../src/gridview/baseComponentGridview.ts | 8 +------- .../src/gridview/basePanelView.ts | 11 ++++++++-- packages/dockview-core/src/panel/types.ts | 2 +- 10 files changed, 65 insertions(+), 28 deletions(-) diff --git a/packages/dockview-core/src/api/dockviewPanelApi.ts b/packages/dockview-core/src/api/dockviewPanelApi.ts index 6ef8d824a..c5a8c1468 100644 --- a/packages/dockview-core/src/api/dockviewPanelApi.ts +++ b/packages/dockview-core/src/api/dockviewPanelApi.ts @@ -106,6 +106,8 @@ export class DockviewPanelApiImpl this._group = group; + + this.addDisposables( this.disposable, this._onDidRendererChange, diff --git a/packages/dockview-core/src/api/panelApi.ts b/packages/dockview-core/src/api/panelApi.ts index 95d35a3b8..53ff4d821 100644 --- a/packages/dockview-core/src/api/panelApi.ts +++ b/packages/dockview-core/src/api/panelApi.ts @@ -1,4 +1,4 @@ -import { Emitter, Event } from '../events'; +import { DockviewEvent, Emitter, Event } from '../events'; import { CompositeDisposable, MutableDisposable } from '../lifecycle'; import { IPanel, Parameters } from '../panel/types'; @@ -51,6 +51,14 @@ export interface PanelApi { * The panel height in pixels */ readonly height: number; + + readonly onWillFocus: Event; +} + +export class WillFocusEvent extends DockviewEvent { + constructor() { + super(); + } } /** @@ -75,8 +83,8 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi { }); readonly onDidFocusChange: Event = this._onDidChangeFocus.event; // - readonly _onFocusEvent = new Emitter(); - readonly onFocusEvent: Event = this._onFocusEvent.event; + readonly _onWillFocus = new Emitter(); + readonly onWillFocus: Event = this._onWillFocus.event; // readonly _onDidVisibilityChange = new Emitter({ replay: true, @@ -101,7 +109,6 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi { readonly _onUpdateParameters = new Emitter(); readonly onUpdateParameters: Event = this._onUpdateParameters.event; - // get isFocused() { return this._isFocused; @@ -144,10 +151,11 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi { this._onDidChangeFocus, this._onDidVisibilityChange, this._onDidActiveChange, - this._onFocusEvent, + this._onWillFocus, this._onActiveChange, this._onVisibilityChange, - this._onUpdateParameters + this._onUpdateParameters, + this._onWillFocus ); } diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts index 123608573..1746f857b 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts @@ -386,9 +386,7 @@ export class TabsContainer return; } - this.group.model.openPanel(panel, { - skipFocus: alreadyFocused, - }); + this.group.model.openPanel(panel); }), tab.onDrop((event) => { this._onDrop.fire({ diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 7595d723a..c9ccdce67 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -1609,10 +1609,9 @@ export class DockviewComponent doSetGroupAndPanelActive( group: DockviewGroupPanel | undefined, - skipFocus?: boolean ): void { const activePanel = this.activePanel; - super.doSetGroupActive(group, skipFocus); + super.doSetGroupActive(group); if (this._activeGroup?.activePanel !== activePanel) { this._onDidActivePanelChange.fire(this._activeGroup?.activePanel); diff --git a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts index 4462add04..d762c2789 100644 --- a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts +++ b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts @@ -323,7 +323,7 @@ export class DockviewGroupPanelModel this.handleDropEvent(event.event, 'center', event.index); }), this.contentContainer.onDidFocus(() => { - this.accessor.doSetGroupActive(this.groupPanel, true); + this.accessor.doSetGroupActive(this.groupPanel); }), this.contentContainer.onDidBlur(() => { // noop @@ -340,6 +340,10 @@ export class DockviewGroupPanelModel ); } + focusContent(): void { + this.contentContainer.element.focus(); + } + initialize(): void { if (this.options?.panels) { this.options.panels.forEach((panel) => { @@ -504,7 +508,6 @@ export class DockviewGroupPanelModel panel: IDockviewPanel, options: { index?: number; - skipFocus?: boolean; skipSetPanelActive?: boolean; skipSetGroupActive?: boolean; } = {} @@ -537,10 +540,7 @@ export class DockviewGroupPanelModel } if (!skipSetGroupActive) { - this.accessor.doSetGroupActive( - this.groupPanel, - !!options.skipFocus - ); + this.accessor.doSetGroupActive(this.groupPanel); } this.updateContainer(); diff --git a/packages/dockview-core/src/dockview/dockviewPanel.ts b/packages/dockview-core/src/dockview/dockviewPanel.ts index 8c01b08c6..44921a173 100644 --- a/packages/dockview-core/src/dockview/dockviewPanel.ts +++ b/packages/dockview-core/src/dockview/dockviewPanel.ts @@ -10,6 +10,7 @@ import { IPanel, PanelUpdateEvent, Parameters } from '../panel/types'; import { IDockviewPanelModel } from './dockviewPanelModel'; import { DockviewComponent } from './dockviewComponent'; import { DockviewPanelRenderer } from '../overlayRenderContainer'; +import { WillFocusEvent } from '../api/panelApi'; export interface IDockviewPanel extends IDisposable, IPanel { readonly view: IDockviewPanelModel; @@ -93,7 +94,24 @@ export class DockviewPanel } focus(): void { - this.api._onFocusEvent.fire(); + /** + * This is a progmatic request of focus - + * We need to tell the active panel that it can choose it's focus + * If the panel doesn't choose the panels container for it + */ + + if (!this.api.isActive) { + this.api.setActive(); + } + + const event = new WillFocusEvent(); + this.api._onWillFocus.fire(event); + + if (event.defaultPrevented) { + return; + } + + this.group.model.focusContent(); } public toJSON(): GroupviewPanelState { diff --git a/packages/dockview-core/src/events.ts b/packages/dockview-core/src/events.ts index 9474ad317..fcbdf87d9 100644 --- a/packages/dockview-core/src/events.ts +++ b/packages/dockview-core/src/events.ts @@ -23,6 +23,17 @@ export namespace Event { }; }; } +export class DockviewEvent { + private _defaultPrevented = false; + + get defaultPrevented(): boolean { + return this._defaultPrevented; + } + + preventDefault(): void { + this._defaultPrevented = true; + } +} class LeakageMonitor { readonly events = new Map, Stacktrace>(); diff --git a/packages/dockview-core/src/gridview/baseComponentGridview.ts b/packages/dockview-core/src/gridview/baseComponentGridview.ts index 2e4159a9b..ff875c572 100644 --- a/packages/dockview-core/src/gridview/baseComponentGridview.ts +++ b/packages/dockview-core/src/gridview/baseComponentGridview.ts @@ -246,22 +246,16 @@ export abstract class BaseGrid return this._groups.get(id)?.value; } - public doSetGroupActive(group: T | undefined, skipFocus?: boolean): void { + public doSetGroupActive(group: T | undefined): void { if (this._activeGroup === group) { return; } if (this._activeGroup) { this._activeGroup.setActive(false); - if (!skipFocus) { - this._activeGroup.focus?.(); - } } if (group) { group.setActive(true); - if (!skipFocus) { - group.focus?.(); - } } this._activeGroup = group; diff --git a/packages/dockview-core/src/gridview/basePanelView.ts b/packages/dockview-core/src/gridview/basePanelView.ts index 19651d5aa..088831aad 100644 --- a/packages/dockview-core/src/gridview/basePanelView.ts +++ b/packages/dockview-core/src/gridview/basePanelView.ts @@ -7,7 +7,7 @@ import { IPanel, Parameters, } from '../panel/types'; -import { PanelApi, PanelApiImpl } from '../api/panelApi'; +import { PanelApi, PanelApiImpl, WillFocusEvent } from '../api/panelApi'; export interface BasePanelViewState { readonly id: string; @@ -84,7 +84,14 @@ export abstract class BasePanelView } focus(): void { - this.api._onFocusEvent.fire(); + const event = new WillFocusEvent(); + this.api._onWillFocus.fire(event); + + if (event.defaultPrevented) { + return; + } + + this._element.focus(); } layout(width: number, height: number): void { diff --git a/packages/dockview-core/src/panel/types.ts b/packages/dockview-core/src/panel/types.ts index 133e39a3b..7cb3e1e92 100644 --- a/packages/dockview-core/src/panel/types.ts +++ b/packages/dockview-core/src/panel/types.ts @@ -22,7 +22,7 @@ export interface IPanel extends IDisposable { layout(width: number, height: number): void; update(event: PanelUpdateEvent): void; toJSON(): object; - focus?(): void; + focus(): void; } export interface IFrameworkPart extends IDisposable { From 807ccf80de5cb58fea238f4ea0525f6d4c59adeb Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Sat, 3 Feb 2024 22:56:38 +0000 Subject: [PATCH 2/3] feat: events cleanup --- .../dockview/dockviewComponent.spec.ts | 337 +++++----- .../dockview/dockviewGroupPanelModel.spec.ts | 11 +- .../dockview/dockviewPanelModel.spec.ts | 86 +-- .../gridview/baseComponentGridview.spec.ts | 27 +- .../src/api/dockviewGroupPanelApi.ts | 15 +- .../dockview-core/src/api/dockviewPanelApi.ts | 65 +- .../components/titlebar/tabsContainer.ts | 8 +- .../src/dockview/dockviewComponent.ts | 576 ++++++++++++------ .../src/dockview/dockviewGroupPanel.ts | 7 + .../src/dockview/dockviewGroupPanelModel.ts | 107 ++-- .../src/dockview/dockviewPanel.ts | 69 ++- .../src/dockview/dockviewPanelModel.ts | 27 +- packages/dockview-core/src/dockview/types.ts | 4 - packages/dockview-core/src/events.ts | 4 + .../src/gridview/baseComponentGridview.ts | 40 +- .../src/gridview/gridviewComponent.ts | 32 + packages/docs/docs/components/dockview.mdx | 7 + .../docs/sandboxes/demo-dockview/src/app.tsx | 396 +++++++++++- .../sandboxes/focus-dockview/package.json | 32 + .../focus-dockview/public/index.html | 44 ++ .../docs/sandboxes/focus-dockview/src/app.tsx | 125 ++++ .../sandboxes/focus-dockview/src/index.tsx | 20 + .../sandboxes/focus-dockview/src/styles.css | 16 + .../sandboxes/focus-dockview/tsconfig.json | 18 + 24 files changed, 1506 insertions(+), 567 deletions(-) create mode 100644 packages/docs/sandboxes/focus-dockview/package.json create mode 100644 packages/docs/sandboxes/focus-dockview/public/index.html create mode 100644 packages/docs/sandboxes/focus-dockview/src/app.tsx create mode 100644 packages/docs/sandboxes/focus-dockview/src/index.tsx create mode 100644 packages/docs/sandboxes/focus-dockview/src/styles.css create mode 100644 packages/docs/sandboxes/focus-dockview/tsconfig.json diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index 51482f0ed..6c7cf922c 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -257,9 +257,17 @@ describe('dockviewComponent', () => { const panel4 = dockview.getGroupPanel('panel4'); const group1 = panel1!.group; - dockview.moveGroupOrPanel(group1, group1.id, 'panel1', 'right'); + + dockview.moveGroupOrPanel({ + from: { groupId: group1.id, panelId: 'panel1' }, + to: { group: group1, position: 'right' }, + }); const group2 = panel1!.group; - dockview.moveGroupOrPanel(group2, group1.id, 'panel3', 'center'); + + dockview.moveGroupOrPanel({ + from: { groupId: group1.id, panelId: 'panel3' }, + to: { group: group2, position: 'center' }, + }); expect(dockview.activeGroup).toBe(group2); expect(dockview.activeGroup!.model.activePanel).toBe(panel3); @@ -309,9 +317,16 @@ describe('dockviewComponent', () => { const panel1 = dockview.getGroupPanel('panel1')!; const panel2 = dockview.getGroupPanel('panel2')!; const group1 = panel1.group; - dockview.moveGroupOrPanel(group1, group1.id, 'panel1', 'right'); + + dockview.moveGroupOrPanel({ + from: { groupId: group1.id, panelId: 'panel1' }, + to: { group: group1, position: 'right' }, + }); const group2 = panel1.group; - dockview.moveGroupOrPanel(group2, group1.id, 'panel3', 'center'); + dockview.moveGroupOrPanel({ + from: { groupId: group1.id, panelId: 'panel3' }, + to: { group: group2, position: 'center' }, + }); expect(dockview.size).toBe(2); expect(dockview.totalPanels).toBe(4); @@ -374,9 +389,16 @@ describe('dockviewComponent', () => { expect(panel4.api.isActive).toBeFalsy(); const group1 = panel1.group; - dockview.moveGroupOrPanel(group1, group1.id, 'panel1', 'right'); + + dockview.moveGroupOrPanel({ + from: { groupId: group1.id, panelId: 'panel1' }, + to: { group: group1, position: 'right' }, + }); const group2 = panel1.group; - dockview.moveGroupOrPanel(group2, group1.id, 'panel3', 'center'); + dockview.moveGroupOrPanel({ + from: { groupId: group1.id, panelId: 'panel3' }, + to: { group: group2, position: 'center' }, + }); expect(dockview.size).toBe(2); expect(panel1.group).toBe(panel3.group); @@ -443,7 +465,10 @@ describe('dockviewComponent', () => { expect(group.model.indexOf(panel1)).toBe(0); expect(group.model.indexOf(panel2)).toBe(1); - dockview.moveGroupOrPanel(group, group.id, 'panel1', 'right'); + dockview.moveGroupOrPanel({ + from: { groupId: group.id, panelId: 'panel1' }, + to: { group, position: 'right' }, + }); expect(dockview.size).toBe(2); expect(dockview.totalPanels).toBe(2); @@ -493,7 +518,10 @@ describe('dockviewComponent', () => { expect(viewQuery.length).toBe(1); const group = dockview.getGroupPanel('panel1')!.group; - dockview.moveGroupOrPanel(group, group.id, 'panel1', 'right'); + dockview.moveGroupOrPanel({ + from: { groupId: group.id, panelId: 'panel1' }, + to: { group, position: 'right' }, + }); viewQuery = container.querySelectorAll( '.branch-node > .split-view-container > .view-container > .view' @@ -908,8 +936,8 @@ describe('dockviewComponent', () => { expect(events).toEqual([ { type: 'ADD_GROUP', group: panel1.group }, - { type: 'ACTIVE_GROUP', group: panel1.group }, { type: 'ADD_PANEL', panel: panel1 }, + { type: 'ACTIVE_GROUP', group: panel1.group }, { type: 'ACTIVE_PANEL', panel: panel1 }, ]); @@ -956,8 +984,8 @@ describe('dockviewComponent', () => { expect(events).toEqual([ { type: 'ADD_GROUP', group: panel4.group }, - { type: 'ACTIVE_GROUP', group: panel4.group }, { type: 'ADD_PANEL', panel: panel4 }, + { type: 'ACTIVE_GROUP', group: panel4.group }, { type: 'ACTIVE_PANEL', panel: panel4 }, ]); @@ -973,36 +1001,24 @@ describe('dockviewComponent', () => { ]); events = []; - dockview.moveGroupOrPanel( - panel2.group!, - panel5.group!.id, - panel5.id, - 'center' - ); + dockview.moveGroupOrPanel({ + from: { groupId: panel5.group.id, panelId: panel5.id }, + to: { group: panel2.group, position: 'center' }, + }); - expect(events).toEqual([ - { type: 'REMOVE_PANEL', panel: panel5 }, - { type: 'ACTIVE_PANEL', panel: panel4 }, - { type: 'ADD_PANEL', panel: panel5 }, - { type: 'ACTIVE_PANEL', panel: panel5 }, - { type: 'ACTIVE_GROUP', group: panel2.group }, - ]); + expect(events).toEqual([{ type: 'ACTIVE_GROUP', group: panel2.group }]); events = []; const groupReferenceBeforeMove = panel4.group; - dockview.moveGroupOrPanel( - panel2.group!, - panel4.group!.id, - panel4.id, - 'center' - ); + dockview.moveGroupOrPanel({ + from: { groupId: panel4.group.id, panelId: panel4.id }, + to: { group: panel2.group, position: 'center' }, + }); expect(events).toEqual([ - { type: 'REMOVE_PANEL', panel: panel4 }, { type: 'REMOVE_GROUP', group: groupReferenceBeforeMove }, - { type: 'ADD_PANEL', panel: panel4 }, { type: 'ACTIVE_PANEL', panel: panel4 }, ]); @@ -1022,8 +1038,8 @@ describe('dockviewComponent', () => { expect(events).toEqual([ { type: 'ADD_GROUP', group: panel6.group }, { type: 'ADD_PANEL', panel: panel6 }, - { type: 'ACTIVE_PANEL', panel: panel6 }, { type: 'ACTIVE_GROUP', group: panel6.group }, + { type: 'ACTIVE_PANEL', panel: panel6 }, ]); events = []; @@ -1038,8 +1054,8 @@ describe('dockviewComponent', () => { expect(events).toEqual([ { type: 'ADD_GROUP', group: panel7.group }, { type: 'ADD_PANEL', panel: panel7 }, - { type: 'ACTIVE_PANEL', panel: panel7 }, { type: 'ACTIVE_GROUP', group: panel7.group }, + { type: 'ACTIVE_PANEL', panel: panel7 }, ]); expect(dockview.activePanel === panel7).toBeTruthy(); @@ -1061,6 +1077,7 @@ describe('dockviewComponent', () => { { type: 'REMOVE_PANEL', panel: panel6 }, { type: 'REMOVE_GROUP', group: panel6Group }, { type: 'ACTIVE_GROUP', group: undefined }, + { type: 'ACTIVE_PANEL', group: undefined }, ]); expect(dockview.size).toBe(0); @@ -1317,12 +1334,10 @@ describe('dockviewComponent', () => { const panel1Spy = jest.spyOn(panel1, 'dispose'); const panel2Spy = jest.spyOn(panel2, 'dispose'); - dockview.moveGroupOrPanel( - panel1.group, - panel2.group.id, - 'panel2', - 'left' - ); + dockview.moveGroupOrPanel({ + from: { groupId: panel2.group.id, panelId: 'panel2' }, + to: { group: panel1.group, position: 'left' }, + }); expect(panel1Spy).not.toHaveBeenCalled(); expect(panel2Spy).not.toHaveBeenCalled(); @@ -1359,12 +1374,10 @@ describe('dockviewComponent', () => { const panel1Spy = jest.spyOn(panel1, 'dispose'); const panel2Spy = jest.spyOn(panel2, 'dispose'); - dockview.moveGroupOrPanel( - panel1.group, - panel2.group.id, - 'panel2', - 'center' - ); + dockview.moveGroupOrPanel({ + from: { groupId: panel2.group.id, panelId: 'panel2' }, + to: { group: panel1.group, position: 'center' }, + }); expect(panel1Spy).not.toHaveBeenCalled(); expect(panel2Spy).not.toHaveBeenCalled(); @@ -1399,13 +1412,10 @@ describe('dockviewComponent', () => { const panel1Spy = jest.spyOn(panel1, 'dispose'); const panel2Spy = jest.spyOn(panel2, 'dispose'); - dockview.moveGroupOrPanel( - panel1.group, - panel1.group.id, - 'panel1', - 'center', - 0 - ); + dockview.moveGroupOrPanel({ + from: { groupId: panel1.group.id, panelId: 'panel1' }, + to: { group: panel1.group, position: 'center', index: 0 }, + }); expect(panel1Spy).not.toHaveBeenCalled(); expect(panel2Spy).not.toHaveBeenCalled(); @@ -1563,12 +1573,10 @@ describe('dockviewComponent', () => { expect(dockview.groups.length).toBe(2); - dockview.moveGroupOrPanel( - panel3.group, - panel1.group.id, - undefined, - 'center' - ); + dockview.moveGroupOrPanel({ + from: { groupId: panel1.group.id }, + to: { group: panel3.group, position: 'center' }, + }); expect(dockview.groups.length).toBe(1); expect(panel1Spy).toBeCalledTimes(1); @@ -1697,7 +1705,7 @@ describe('dockviewComponent', () => { expect(activeGroup.length).toBe(1); expect(addPanel.length).toBe(5); expect(removePanel.length).toBe(0); - expect(activePanel.length).toBe(5); + expect(activePanel.length).toBe(1); expect(layoutChange).toBe(1); expect(layoutChangeFromJson).toBe(1); @@ -2728,32 +2736,26 @@ describe('dockviewComponent', () => { expect(dockview.element.querySelectorAll('.view').length).toBe(1); - dockview.moveGroupOrPanel( - panel3.group, - panel3.group.id, - panel3.id, - 'right' - ); + dockview.moveGroupOrPanel({ + from: { groupId: panel3.group.id, panelId: panel3.id }, + to: { group: panel3.group, position: 'right' }, + }); expect(dockview.groups.length).toBe(2); expect(dockview.element.querySelectorAll('.view').length).toBe(2); - dockview.moveGroupOrPanel( - panel3.group, - panel2.group.id, - panel2.id, - 'bottom' - ); + dockview.moveGroupOrPanel({ + from: { groupId: panel2.group.id, panelId: panel2.id }, + to: { group: panel3.group, position: 'bottom' }, + }); expect(dockview.groups.length).toBe(3); expect(dockview.element.querySelectorAll('.view').length).toBe(4); - dockview.moveGroupOrPanel( - panel2.group, - panel1.group.id, - panel1.id, - 'center' - ); + dockview.moveGroupOrPanel({ + from: { groupId: panel1.group.id, panelId: panel1.id }, + to: { group: panel2.group, position: 'center' }, + }); expect(dockview.groups.length).toBe(2); @@ -3463,12 +3465,10 @@ describe('dockviewComponent', () => { expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); - dockview.moveGroupOrPanel( - panel1.group, - panel2.group.id, - undefined, - 'right' - ); + dockview.moveGroupOrPanel({ + from: { groupId: panel2.group.id }, + to: { group: panel1.group, position: 'right' }, + }); expect(panel1.group.api.location.type).toBe('grid'); expect(panel2.group.api.location.type).toBe('grid'); @@ -3508,12 +3508,10 @@ describe('dockviewComponent', () => { expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); - dockview.moveGroupOrPanel( - panel1.group, - panel2.group.id, - undefined, - 'center' - ); + dockview.moveGroupOrPanel({ + from: { groupId: panel2.group.id }, + to: { group: panel1.group, position: 'center' }, + }); expect(panel1.group.api.location.type).toBe('grid'); expect(panel2.group.api.location.type).toBe('grid'); @@ -3560,12 +3558,10 @@ describe('dockviewComponent', () => { expect(dockview.groups.length).toBe(3); expect(dockview.panels.length).toBe(3); - dockview.moveGroupOrPanel( - panel2.group, - panel3.group.id, - undefined, - 'center' - ); + dockview.moveGroupOrPanel({ + from: { groupId: panel3.group.id }, + to: { group: panel2.group, position: 'center' }, + }); expect(panel1.group.api.location.type).toBe('grid'); expect(panel2.group.api.location.type).toBe('floating'); @@ -3613,12 +3609,10 @@ describe('dockviewComponent', () => { expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); - dockview.moveGroupOrPanel( - panel1.group, - panel2.group.id, - undefined, - 'right' - ); + dockview.moveGroupOrPanel({ + from: { groupId: panel2.group.id }, + to: { group: panel1.group, position: 'right' }, + }); expect(panel1.group.api.location.type).toBe('grid'); expect(panel2.group.api.location.type).toBe('grid'); @@ -3666,12 +3660,10 @@ describe('dockviewComponent', () => { expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); - dockview.moveGroupOrPanel( - panel1.group, - panel2.group.id, - undefined, - 'center' - ); + dockview.moveGroupOrPanel({ + from: { groupId: panel2.group.id }, + to: { group: panel1.group, position: 'center' }, + }); expect(panel1.group.api.location.type).toBe('grid'); expect(panel2.group.api.location.type).toBe('grid'); @@ -3726,12 +3718,10 @@ describe('dockviewComponent', () => { expect(dockview.groups.length).toBe(3); expect(dockview.panels.length).toBe(4); - dockview.moveGroupOrPanel( - panel4.group, - panel2.group.id, - undefined, - 'center' - ); + dockview.moveGroupOrPanel({ + from: { groupId: panel2.group.id }, + to: { group: panel4.group, position: 'center' }, + }); expect(panel1.group.api.location.type).toBe('grid'); expect(panel2.group.api.location.type).toBe('floating'); @@ -3773,12 +3763,10 @@ describe('dockviewComponent', () => { expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); - dockview.moveGroupOrPanel( - panel1.group, - panel2.group.id, - panel2.id, - 'right' - ); + dockview.moveGroupOrPanel({ + from: { groupId: panel2.group.id, panelId: panel2.id }, + to: { group: panel1.group, position: 'right' }, + }); expect(panel1.group.api.location.type).toBe('grid'); expect(panel2.group.api.location.type).toBe('grid'); @@ -3818,12 +3806,10 @@ describe('dockviewComponent', () => { expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(2); - dockview.moveGroupOrPanel( - panel1.group, - panel2.group.id, - panel2.id, - 'center' - ); + dockview.moveGroupOrPanel({ + from: { groupId: panel2.group.id, panelId: panel2.id }, + to: { group: panel1.group, position: 'center' }, + }); expect(panel1.group.api.location.type).toBe('grid'); expect(panel2.group.api.location.type).toBe('grid'); @@ -3870,12 +3856,10 @@ describe('dockviewComponent', () => { expect(dockview.groups.length).toBe(3); expect(dockview.panels.length).toBe(3); - dockview.moveGroupOrPanel( - panel2.group, - panel3.group.id, - panel3.id, - 'center' - ); + dockview.moveGroupOrPanel({ + from: { groupId: panel3.group.id, panelId: panel3.id }, + to: { group: panel2.group, position: 'center' }, + }); expect(panel1.group.api.location.type).toBe('grid'); expect(panel2.group.api.location.type).toBe('floating'); @@ -3923,12 +3907,10 @@ describe('dockviewComponent', () => { expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); - dockview.moveGroupOrPanel( - panel1.group, - panel2.group.id, - panel2.id, - 'right' - ); + dockview.moveGroupOrPanel({ + from: { groupId: panel2.group.id, panelId: panel2.id }, + to: { group: panel1.group, position: 'right' }, + }); expect(panel1.group.api.location.type).toBe('grid'); expect(panel2.group.api.location.type).toBe('grid'); @@ -3976,12 +3958,10 @@ describe('dockviewComponent', () => { expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); - dockview.moveGroupOrPanel( - panel1.group, - panel2.group.id, - panel2.id, - 'center' - ); + dockview.moveGroupOrPanel({ + from: { groupId: panel2.group.id, panelId: panel2.id }, + to: { group: panel1.group, position: 'center' }, + }); expect(panel1.group.api.location.type).toBe('grid'); expect(panel2.group.api.location.type).toBe('grid'); @@ -4036,12 +4016,10 @@ describe('dockviewComponent', () => { expect(dockview.groups.length).toBe(3); expect(dockview.panels.length).toBe(4); - dockview.moveGroupOrPanel( - panel4.group, - panel2.group.id, - panel2.id, - 'center' - ); + dockview.moveGroupOrPanel({ + from: { groupId: panel2.group.id, panelId: panel2.id }, + to: { group: panel4.group, position: 'center' }, + }); expect(panel1.group.api.location.type).toBe('grid'); expect(panel2.group.api.location.type).toBe('floating'); @@ -4090,12 +4068,10 @@ describe('dockviewComponent', () => { expect(dockview.groups.length).toBe(3); expect(dockview.panels.length).toBe(3); - dockview.moveGroupOrPanel( - panel3.group, - panel1.group.id, - panel1.id, - 'center' - ); + dockview.moveGroupOrPanel({ + from: { groupId: panel1.group.id, panelId: panel1.id }, + to: { group: panel3.group, position: 'center' }, + }); expect(panel1.group.api.location.type).toBe('floating'); expect(panel2.group.api.location.type).toBe('grid'); @@ -4142,12 +4118,10 @@ describe('dockviewComponent', () => { expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); - dockview.moveGroupOrPanel( - panel3.group, - panel1.group.id, - panel1.id, - 'center' - ); + dockview.moveGroupOrPanel({ + from: { groupId: panel1.group.id, panelId: panel1.id }, + to: { group: panel3.group, position: 'center' }, + }); expect(panel1.group.api.location.type).toBe('floating'); expect(panel2.group.api.location.type).toBe('grid'); @@ -4195,12 +4169,10 @@ describe('dockviewComponent', () => { expect(dockview.groups.length).toBe(3); expect(dockview.panels.length).toBe(3); - dockview.moveGroupOrPanel( - panel3.group, - panel1.group.id, - undefined, - 'center' - ); + dockview.moveGroupOrPanel({ + from: { groupId: panel1.group.id }, + to: { group: panel3.group, position: 'center' }, + }); expect(panel1.group.api.location.type).toBe('floating'); expect(panel2.group.api.location.type).toBe('grid'); @@ -4247,12 +4219,10 @@ describe('dockviewComponent', () => { expect(dockview.groups.length).toBe(2); expect(dockview.panels.length).toBe(3); - dockview.moveGroupOrPanel( - panel3.group, - panel1.group.id, - undefined, - 'center' - ); + dockview.moveGroupOrPanel({ + from: { groupId: panel1.group.id }, + to: { group: panel3.group, position: 'center' }, + }); expect(panel1.group.api.location.type).toBe('floating'); expect(panel2.group.api.location.type).toBe('floating'); @@ -4427,6 +4397,7 @@ describe('dockviewComponent', () => { document: fromPartial({ body: document.createElement('body'), }), + focus: jest.fn(), addEventListener: jest .fn() .mockImplementation((name, cb) => { @@ -4560,12 +4531,10 @@ describe('dockviewComponent', () => { expect(dockview.groups.length).toBe(3); expect(dockview.panels.length).toBe(3); - dockview.moveGroupOrPanel( - panel3.api.group, - panel2.api.group.id, - panel2.api.id, - 'right' - ); + dockview.moveGroupOrPanel({ + from: { groupId: panel2.group.id, panelId: panel2.id }, + to: { group: panel3.group, position: 'right' }, + }); expect(panel1.group.api.location.type).toBe('popout'); expect(panel2.group.api.location.type).toBe('grid'); @@ -4573,12 +4542,10 @@ describe('dockviewComponent', () => { expect(dockview.groups.length).toBe(4); expect(dockview.panels.length).toBe(3); - dockview.moveGroupOrPanel( - panel3.api.group, - panel1.api.group.id, - panel1.api.id, - 'center' - ); + dockview.moveGroupOrPanel({ + from: { groupId: panel1.group.id, panelId: panel1.id }, + to: { group: panel3.group, position: 'center' }, + }); expect(panel1.group.api.location.type).toBe('grid'); expect(panel2.group.api.location.type).toBe('grid'); diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts index 704a26009..5e523f06b 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts @@ -196,7 +196,14 @@ export class TestPanel implements IDockviewPanel { this._params = params; } - updateParentGroup(group: DockviewGroupPanel, isGroupActive: boolean): void { + updateParentGroup( + group: DockviewGroupPanel, + options: { isGroupActive: boolean } + ): void { + // + } + + runEvents(): void { // } @@ -624,7 +631,7 @@ describe('dockviewGroupPanelModel', () => { renderer: 'onlyWhenVisibile', } as any); - cut.openPanel(panel3, { skipSetPanelActive: true }); + cut.openPanel(panel3, { skipRender: true }); expect(contentContainer.length).toBe(1); expect(contentContainer.item(0)).toBe(panel2.view.content.element); diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewPanelModel.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewPanelModel.spec.ts index 01fb2813f..b54ddadcc 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewPanelModel.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewPanelModel.spec.ts @@ -18,8 +18,8 @@ describe('dockviewGroupPanel', () => { element: document.createElement('div'), dispose: jest.fn(), update: jest.fn(), - onGroupChange: jest.fn(), - onPanelVisibleChange: jest.fn(), + // onGroupChange: jest.fn(), + // onPanelVisibleChange: jest.fn(), }; return partial as IContentRenderer; }); @@ -29,8 +29,8 @@ describe('dockviewGroupPanel', () => { element: document.createElement('div'), dispose: jest.fn(), update: jest.fn(), - onGroupChange: jest.fn(), - onPanelVisibleChange: jest.fn(), + // onGroupChange: jest.fn(), + // onPanelVisibleChange: jest.fn(), }; return partial as IContentRenderer; }); @@ -82,51 +82,51 @@ describe('dockviewGroupPanel', () => { expect(cut.tab.update).toHaveBeenCalled(); }); - test('that events are fired', () => { - const cut = new DockviewPanelModel( - new accessorMock(), - 'id', - 'contentComponent', - 'tabComponent' - ); + // test('that events are fired', () => { + // const cut = new DockviewPanelModel( + // new accessorMock(), + // 'id', + // 'contentComponent', + // 'tabComponent' + // ); - const group1 = jest.fn() as any; - const group2 = jest.fn() as any; - cut.updateParentGroup(group1, false); + // const group1 = jest.fn() as any; + // const group2 = jest.fn() as any; + // cut.updateParentGroup(group1, false); - expect(cut.content.onGroupChange).toHaveBeenNthCalledWith(1, group1); - expect(cut.tab.onGroupChange).toHaveBeenNthCalledWith(1, group1); - expect(cut.content.onPanelVisibleChange).toHaveBeenNthCalledWith( - 1, - false - ); - expect(cut.tab.onPanelVisibleChange).toHaveBeenNthCalledWith(1, false); - expect(cut.content.onGroupChange).toHaveBeenCalledTimes(1); - expect(cut.tab.onGroupChange).toHaveBeenCalledTimes(1); - expect(cut.content.onPanelVisibleChange).toHaveBeenCalledTimes(1); - expect(cut.tab.onPanelVisibleChange).toHaveBeenCalledTimes(1); + // expect(cut.content.onGroupChange).toHaveBeenNthCalledWith(1, group1); + // expect(cut.tab.onGroupChange).toHaveBeenNthCalledWith(1, group1); + // expect(cut.content.onPanelVisibleChange).toHaveBeenNthCalledWith( + // 1, + // false + // ); + // expect(cut.tab.onPanelVisibleChange).toHaveBeenNthCalledWith(1, false); + // expect(cut.content.onGroupChange).toHaveBeenCalledTimes(1); + // expect(cut.tab.onGroupChange).toHaveBeenCalledTimes(1); + // expect(cut.content.onPanelVisibleChange).toHaveBeenCalledTimes(1); + // expect(cut.tab.onPanelVisibleChange).toHaveBeenCalledTimes(1); - cut.updateParentGroup(group1, true); + // cut.updateParentGroup(group1, true); - expect(cut.content.onPanelVisibleChange).toHaveBeenNthCalledWith( - 2, - true - ); - expect(cut.tab.onPanelVisibleChange).toHaveBeenNthCalledWith(2, true); - expect(cut.content.onGroupChange).toHaveBeenCalledTimes(1); - expect(cut.tab.onGroupChange).toHaveBeenCalledTimes(1); - expect(cut.content.onPanelVisibleChange).toHaveBeenCalledTimes(2); - expect(cut.tab.onPanelVisibleChange).toHaveBeenCalledTimes(2); + // expect(cut.content.onPanelVisibleChange).toHaveBeenNthCalledWith( + // 2, + // true + // ); + // expect(cut.tab.onPanelVisibleChange).toHaveBeenNthCalledWith(2, true); + // expect(cut.content.onGroupChange).toHaveBeenCalledTimes(1); + // expect(cut.tab.onGroupChange).toHaveBeenCalledTimes(1); + // expect(cut.content.onPanelVisibleChange).toHaveBeenCalledTimes(2); + // expect(cut.tab.onPanelVisibleChange).toHaveBeenCalledTimes(2); - cut.updateParentGroup(group2, true); + // cut.updateParentGroup(group2, true); - expect(cut.content.onGroupChange).toHaveBeenNthCalledWith(2, group2); - expect(cut.tab.onGroupChange).toHaveBeenNthCalledWith(2, group2); - expect(cut.content.onGroupChange).toHaveBeenCalledTimes(2); - expect(cut.tab.onGroupChange).toHaveBeenCalledTimes(2); - expect(cut.content.onPanelVisibleChange).toHaveBeenCalledTimes(2); - expect(cut.tab.onPanelVisibleChange).toHaveBeenCalledTimes(2); - }); + // expect(cut.content.onGroupChange).toHaveBeenNthCalledWith(2, group2); + // expect(cut.tab.onGroupChange).toHaveBeenNthCalledWith(2, group2); + // expect(cut.content.onGroupChange).toHaveBeenCalledTimes(2); + // expect(cut.tab.onGroupChange).toHaveBeenCalledTimes(2); + // expect(cut.content.onPanelVisibleChange).toHaveBeenCalledTimes(2); + // expect(cut.tab.onPanelVisibleChange).toHaveBeenCalledTimes(2); + // }); test('that the default tab is created', () => { accessorMock = jest.fn(() => { diff --git a/packages/dockview-core/src/__tests__/gridview/baseComponentGridview.spec.ts b/packages/dockview-core/src/__tests__/gridview/baseComponentGridview.spec.ts index c0d1a9754..75e351d9c 100644 --- a/packages/dockview-core/src/__tests__/gridview/baseComponentGridview.spec.ts +++ b/packages/dockview-core/src/__tests__/gridview/baseComponentGridview.spec.ts @@ -114,17 +114,17 @@ describe('baseComponentGridview', () => { proportionalLayout: true, }); - const events: (TestPanel | undefined)[] = []; + const events: { type: string; panel: TestPanel | undefined }[] = []; const disposable = new CompositeDisposable( - cut.onDidAddGroup((event) => { - events.push(event); + cut.onDidAdd((event) => { + events.push({ type: 'add', panel: event }); }), - cut.onDidRemoveGroup((event) => { - events.push(event); + cut.onDidRemove((event) => { + events.push({ type: 'remove', panel: event }); }), - cut.onDidActiveGroupChange((event) => { - events.push(event); + cut.onDidActiveChange((event) => { + events.push({ type: 'active', panel: event }); }) ); @@ -141,9 +141,8 @@ describe('baseComponentGridview', () => { cut.doAddGroup(panel1); - expect(events.length).toBe(2); - expect(events[0]).toBe(panel1); - expect(events[1]).toBe(panel1); + expect(events.length).toBe(1); + expect(events[0]).toEqual({ type: 'add', panel: panel1 }); const panel2 = new TestPanel( 'id', @@ -158,12 +157,12 @@ describe('baseComponentGridview', () => { cut.doAddGroup(panel2); - expect(events.length).toBe(4); - expect(events[2]).toBe(panel2); + expect(events.length).toBe(2); + expect(events[1]).toEqual({ type: 'add', panel: panel2 }); cut.doRemoveGroup(panel1); - expect(events.length).toBe(5); - expect(events[4]).toBe(panel1); + expect(events.length).toBe(3); + expect(events[2]).toEqual({ type: 'remove', panel: panel1 }); disposable.dispose(); cut.dispose(); diff --git a/packages/dockview-core/src/api/dockviewGroupPanelApi.ts b/packages/dockview-core/src/api/dockviewGroupPanelApi.ts index a5a8bb371..347b09660 100644 --- a/packages/dockview-core/src/api/dockviewGroupPanelApi.ts +++ b/packages/dockview-core/src/api/dockviewGroupPanelApi.ts @@ -63,12 +63,15 @@ export class DockviewGroupPanelApiImpl extends GridviewPanelApiImpl { direction: positionToDirection(options.position ?? 'right'), }); - this.accessor.moveGroupOrPanel( - group, - this._group.id, - undefined, - options.group ? options.position ?? 'center' : 'center' - ); + this.accessor.moveGroupOrPanel({ + from: { groupId: this._group.id }, + to: { + group, + position: options.group + ? options.position ?? 'center' + : 'center', + }, + }); } maximize(): void { diff --git a/packages/dockview-core/src/api/dockviewPanelApi.ts b/packages/dockview-core/src/api/dockviewPanelApi.ts index 775d5e2a0..3bace3792 100644 --- a/packages/dockview-core/src/api/dockviewPanelApi.ts +++ b/packages/dockview-core/src/api/dockviewPanelApi.ts @@ -14,7 +14,15 @@ export interface TitleEvent { } export interface RendererChangedEvent { - renderer: DockviewPanelRenderer; + readonly renderer: DockviewPanelRenderer; +} + +export interface ActiveGroupEvent { + readonly isActive: boolean; +} + +export interface GroupChangedEvent { + // empty } export interface DockviewPanelApi @@ -27,8 +35,8 @@ export interface DockviewPanelApi readonly isGroupActive: boolean; readonly renderer: DockviewPanelRenderer; readonly title: string | undefined; - readonly onDidActiveGroupChange: Event; - readonly onDidGroupChange: Event; + readonly onDidActiveGroupChange: Event; + readonly onDidGroupChange: Event; readonly onDidRendererChange: Event; readonly location: DockviewGroupLocation; readonly onDidLocationChange: Event; @@ -58,10 +66,10 @@ export class DockviewPanelApiImpl readonly _onDidTitleChange = new Emitter(); readonly onDidTitleChange = this._onDidTitleChange.event; - private readonly _onDidActiveGroupChange = new Emitter(); + private readonly _onDidActiveGroupChange = new Emitter(); readonly onDidActiveGroupChange = this._onDidActiveGroupChange.event; - private readonly _onDidGroupChange = new Emitter(); + private readonly _onDidGroupChange = new Emitter(); readonly onDidGroupChange = this._onDidGroupChange.event; readonly _onDidRendererChange = new Emitter(); @@ -93,23 +101,39 @@ export class DockviewPanelApiImpl set group(value: DockviewGroupPanel) { const isOldGroupActive = this.isGroupActive; - this._group = value; + if (this._group !== value) { + this._group = value; - this._onDidGroupChange.fire(); + this._onDidGroupChange.fire({}); + + let _trackGroupActive = isOldGroupActive; // prevent duplicate events with same state - if (this._group) { this.groupEventsDisposable.value = new CompositeDisposable( this.group.api.onDidLocationChange((event) => { + if (this.group !== this.panel.group) { + return; + } this._onDidLocationChange.fire(event); }), this.group.api.onDidActiveChange(() => { - this._onDidActiveGroupChange.fire(); + if (this.group !== this.panel.group) { + return; + } + + if (_trackGroupActive !== this.isGroupActive) { + _trackGroupActive = this.isGroupActive; + this._onDidActiveGroupChange.fire({ + isActive: this.isGroupActive, + }); + } }) ); - if (this.isGroupActive !== isOldGroupActive) { - this._onDidActiveGroupChange.fire(); - } + // if (this.isGroupActive !== isOldGroupActive) { + // this._onDidActiveGroupChange.fire({ + // isActive: this.isGroupActive, + // }); + // } this._onDidLocationChange.fire({ location: this.group.api.location, @@ -132,8 +156,6 @@ export class DockviewPanelApiImpl this._group = group; - - this.addDisposables( this.groupEventsDisposable, this._onDidRendererChange, @@ -153,13 +175,14 @@ export class DockviewPanelApiImpl position?: Position; index?: number; }): void { - this.accessor.moveGroupOrPanel( - options.group, - this._group.id, - this.panel.id, - options.position ?? 'center', - options.index - ); + this.accessor.moveGroupOrPanel({ + from: { groupId: this._group.id, panelId: this.panel.id }, + to: { + group: options.group, + position: options.position ?? 'center', + index: options.index, + }, + }); } setTitle(title: string): void { diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts index ff1030e18..9533252e0 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabsContainer.ts @@ -390,17 +390,15 @@ export class TabsContainer return; } - const alreadyFocused = - panel.id === this.group.model.activePanel?.id && - this.group.model.isContentFocused; - const isLeftClick = event.button === 0; if (!isLeftClick || event.defaultPrevented) { return; } - this.group.model.openPanel(panel); + if (this.group.activePanel !== panel) { + this.group.model.openPanel(panel); + } }), tab.onDrop((event) => { this._onDrop.fire({ diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 7758dc1cd..b782a76ab 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -232,6 +232,23 @@ export type DockviewComponentUpdateOptions = Pick< | 'disableDnd' >; +type MoveGroupOptions = { + from: { group: DockviewGroupPanel }; + to: { group: DockviewGroupPanel; position: Position }; +}; + +type MoveGroupOrPanelOptions = { + from: { + groupId: string; + panelId?: string; + }; + to: { + group: DockviewGroupPanel; + position: Position; + index?: number; + }; +}; + export interface IDockviewComponent extends IBaseGrid { readonly activePanel: IDockviewPanel | undefined; readonly totalPanels: number; @@ -241,13 +258,8 @@ export interface IDockviewComponent extends IBaseGrid { readonly onWillShowOverlay: Event; readonly orientation: Orientation; updateOptions(options: DockviewComponentUpdateOptions): void; - moveGroupOrPanel( - referenceGroup: DockviewGroupPanel, - groupId: string, - itemId: string, - target: Position, - index?: number - ): void; + moveGroupOrPanel(options: MoveGroupOrPanelOptions): void; + moveGroup(options: MoveGroupOptions): void; doSetGroupActive: (group: DockviewGroupPanel, skipFocus?: boolean) => void; removeGroup: (group: DockviewGroupPanel) => void; options: DockviewComponentOptions; @@ -287,6 +299,9 @@ export interface IDockviewComponent extends IBaseGrid { onWillClose?: (event: { id: string; window: Window }) => void; } ): Promise; + readonly onDidRemoveGroup: Event; + readonly onDidAddGroup: Event; + readonly onDidActiveGroupChange: Event; } export class DockviewComponent @@ -335,6 +350,10 @@ export class DockviewComponent readonly onDidActivePanelChange: Event = this._onDidActivePanelChange.event; + private readonly _onDidMovePanel = new Emitter<{ + panel: IDockviewPanel; + }>(); + private readonly _floatingGroups: DockviewFloatingGroupPanel[] = []; private readonly _popoutGroups: { window: PopoutWindow; @@ -344,6 +363,22 @@ export class DockviewComponent }[] = []; private readonly _rootDropTarget: Droptarget; + private _ignoreEvents = 0; + + private readonly _onDidRemoveGroup = new Emitter(); + readonly onDidRemoveGroup: Event = + this._onDidRemoveGroup.event; + + protected readonly _onDidAddGroup = new Emitter(); + readonly onDidAddGroup: Event = + this._onDidAddGroup.event; + + private readonly _onDidActiveGroupChange = new Emitter< + DockviewGroupPanel | undefined + >(); + readonly onDidActiveGroupChange: Event = + this._onDidActiveGroupChange.event; + get orientation(): Orientation { return this.gridview.orientation; } @@ -404,9 +439,28 @@ export class DockviewComponent this._onDidLayoutFromJSON, this._onDidDrop, this._onWillDrop, + this._onDidMovePanel, + this._onDidAddGroup, + this._onDidRemoveGroup, + this._onDidActiveGroupChange, + this.onDidAdd((event) => { + if (!this._moving) { + this._onDidAddGroup.fire(event); + } + }), + this.onDidRemove((event) => { + if (!this._moving) { + this._onDidRemoveGroup.fire(event); + } + }), + this.onDidActiveChange((event) => { + if (!this._moving) { + this._onDidActiveGroupChange.fire(event); + } + }), Event.any( - this.onDidAddGroup, - this.onDidRemoveGroup + this.onDidAdd, + this.onDidRemove )(() => { this.updateWatermark(); }), @@ -515,12 +569,16 @@ export class DockviewComponent const data = getPanelData(); if (data) { - this.moveGroupOrPanel( - this.orthogonalize(event.position), - data.groupId, - data.panelId ?? undefined, - 'center' - ); + this.moveGroupOrPanel({ + from: { + groupId: data.groupId, + panelId: data.panelId ?? undefined, + }, + to: { + group: this.orthogonalize(event.position), + position: 'center', + }, + }); } else { this._onDidDrop.fire( new DockviewDidDropEvent({ @@ -574,7 +632,7 @@ export class DockviewComponent panels.forEach((panel) => { options.to.model.openPanel(panel, { - skipSetPanelActive: activePanel !== panel, + skipRender: activePanel !== panel, }); }); } @@ -651,16 +709,27 @@ export class DockviewComponent this.createGroup({ id: groupId }); group.model.renderContainer = overlayRenderContainer; - if (itemToPopout instanceof DockviewPanel) { - const panel = - referenceGroup.model.removePanel(itemToPopout); - group.model.openPanel(panel); - } else { - moveGroupWithoutDestroying({ - from: referenceGroup, - to: group, - }); - referenceGroup.api.setHidden(true); + if (!options?.overridePopoutGroup) { + this._onDidAddGroup.fire(group); + } + + const isMoving = this._moving; + + try { + this._moving = true; + if (itemToPopout instanceof DockviewPanel) { + const panel = + referenceGroup.model.removePanel(itemToPopout); + group.model.openPanel(panel); + } else { + moveGroupWithoutDestroying({ + from: referenceGroup, + to: group, + }); + referenceGroup.api.setHidden(true); + } + } finally { + this._moving = isMoving; } popoutContainer.classList.add('dv-dockview'); @@ -674,6 +743,17 @@ export class DockviewComponent getWindow: () => _window.window!, }; + popoutWindowDisposable.addDisposables( + group.api.onDidActiveChange((event) => { + if (event.isActive) { + _window.window?.focus(); + } + }), + group.api.onWillFocus(() => { + _window.window?.focus(); + }) + ); + const value = { window: _window, popoutGroup: group, @@ -697,10 +777,15 @@ export class DockviewComponent overlayRenderContainer, Disposable.from(() => { if (this.getPanel(referenceGroup.id)) { - moveGroupWithoutDestroying({ - from: group, - to: referenceGroup, - }); + try { + this._moving = true; + moveGroupWithoutDestroying({ + from: group, + to: referenceGroup, + }); + } finally { + this._moving = isMoving; + } if (referenceGroup.api.isHidden) { referenceGroup.api.setHidden(false); @@ -718,6 +803,7 @@ export class DockviewComponent this.overlayRenderContainer; removedGroup.model.location = { type: 'grid' }; this.doAddGroup(removedGroup, [0]); + this.doSetGroupAndPanelActive(removedGroup); } }) ); @@ -733,19 +819,27 @@ export class DockviewComponent addFloatingGroup( item: DockviewPanel | DockviewGroupPanel, coord?: { x?: number; y?: number; height?: number; width?: number }, - options?: { skipRemoveGroup?: boolean; inDragMode: boolean } + options?: { + skipRemoveGroup?: boolean; + inDragMode: boolean; + skipActiveGroup?: boolean; + } ): void { let group: DockviewGroupPanel; if (item instanceof DockviewPanel) { group = this.createGroup(); + this._onDidAddGroup.fire(group); - this.removePanel(item, { - removeEmptyGroup: true, - skipDispose: true, - }); + this.movingLock(() => + this.removePanel(item, { + removeEmptyGroup: true, + skipDispose: true, + skipSetActiveGroup: true, + }) + ); - group.model.openPanel(item); + group.model.openPanel(item, { skipSetGroupActive: true }); } else { group = item; @@ -841,6 +935,11 @@ export class DockviewComponent ); this._floatingGroups.push(floatingGroupPanel); + + if (!options?.skipActiveGroup) { + this.doSetGroupAndPanelActive(group); + } + this.updateWatermark(); } @@ -952,8 +1051,8 @@ export class DockviewComponent } setActivePanel(panel: IDockviewPanel): void { - this.doSetGroupActive(panel.group); panel.group.model.openPanel(panel); + this.doSetGroupAndPanelActive(panel.group); } moveToNext(options: MovementOptions = {}): void { @@ -1108,7 +1207,7 @@ export class DockviewComponent activeView === panel.id; group.model.openPanel(panel, { - skipSetPanelActive: !isActive, + skipRender: !isActive, skipSetGroupActive: true, }); } @@ -1241,10 +1340,6 @@ export class DockviewComponent this.doSetGroupAndPanelActive(undefined); } - if (hasActivePanel) { - this._onDidActivePanelChange.fire(undefined); - } - this.gridview.clear(); } @@ -1301,8 +1396,10 @@ export class DockviewComponent const group = this.orthogonalize( directionToPosition(options.position.direction) ); + const panel = this.createPanel(options, group); group.model.openPanel(panel); + this.doSetGroupAndPanelActive(group); return panel; } } else { @@ -1318,26 +1415,30 @@ export class DockviewComponent if (options.floating) { const group = this.createGroup(); + this._onDidAddGroup.fire(group); + const o = typeof options.floating === 'object' && options.floating !== null ? options.floating : {}; + this.addFloatingGroup(group, o, { inDragMode: false, skipRemoveGroup: true, + skipActiveGroup: true, }); - this._onDidAddGroup.fire(group); panel = this.createPanel(options, group); + group.model.openPanel(panel); - this.doSetGroupAndPanelActive(group); } else if ( referenceGroup.api.location.type === 'floating' || target === 'center' ) { panel = this.createPanel(options, referenceGroup); referenceGroup.model.openPanel(panel); + this.doSetGroupAndPanelActive(referenceGroup); } else { const location = getGridLocation(referenceGroup.element); const relativeLocation = getRelativeLocation( @@ -1348,30 +1449,31 @@ export class DockviewComponent const group = this.createGroupAtLocation(relativeLocation); panel = this.createPanel(options, group); group.model.openPanel(panel); + this.doSetGroupAndPanelActive(group); } } else if (options.floating) { const group = this.createGroup(); + this._onDidAddGroup.fire(group); + const o = typeof options.floating === 'object' && options.floating !== null ? options.floating : {}; + this.addFloatingGroup(group, o, { inDragMode: false, skipRemoveGroup: true, + skipActiveGroup: true, }); - this._onDidAddGroup.fire(group); - + panel = this.createPanel(options, group); + group.model.openPanel(panel); + } else { + const group = this.createGroupAtLocation(); panel = this.createPanel(options, group); group.model.openPanel(panel); this.doSetGroupAndPanelActive(group); - } else { - const group = this.createGroupAtLocation(); - - panel = this.createPanel(options, group); - - group.model.openPanel(panel); } return panel; @@ -1379,7 +1481,11 @@ export class DockviewComponent removePanel( panel: IDockviewPanel, - options: { removeEmptyGroup: boolean; skipDispose: boolean } = { + options: { + removeEmptyGroup: boolean; + skipDispose: boolean; + skipSetActiveGroup?: boolean; + } = { removeEmptyGroup: true, skipDispose: false, } @@ -1392,7 +1498,9 @@ export class DockviewComponent ); } - group.model.removePanel(panel); + group.model.removePanel(panel, { + skipSetActiveGroup: options.skipSetActiveGroup, + }); if (!options.skipDispose) { this.overlayRenderContainer.detatch(panel); @@ -1400,7 +1508,7 @@ export class DockviewComponent } if (group.size === 0 && options.removeEmptyGroup) { - this.removeGroup(group); + this.removeGroup(group, { skipActive: options.skipSetActiveGroup }); } } @@ -1486,6 +1594,7 @@ export class DockviewComponent const group = this.orthogonalize( directionToPosition(options.direction) ); + this.doSetGroupAndPanelActive(group); return group; } @@ -1498,9 +1607,11 @@ export class DockviewComponent target ); this.doAddGroup(group, relativeLocation); + this.doSetGroupAndPanelActive(group); return group; } else { this.doAddGroup(group); + this.doSetGroupAndPanelActive(group); return group; } } @@ -1514,22 +1625,7 @@ export class DockviewComponent } | undefined ): void { - const panels = [...group.panels]; // reassign since group panels will mutate - - for (const panel of panels) { - this.removePanel(panel, { - removeEmptyGroup: false, - skipDispose: options?.skipDispose ?? false, - }); - } - - const activePanel = this.activePanel; - this.doRemoveGroup(group, options); - - if (this.activePanel !== activePanel) { - this._onDidActivePanelChange.fire(this.activePanel); - } } protected override doRemoveGroup( @@ -1542,6 +1638,19 @@ export class DockviewComponent } | undefined ): DockviewGroupPanel { + const panels = [...group.panels]; // reassign since group panels will mutate + + if (!options?.skipDispose) { + for (const panel of panels) { + this.removePanel(panel, { + removeEmptyGroup: false, + skipDispose: options?.skipDispose ?? false, + }); + } + } + + const activePanel = this.activePanel; + if (group.api.location.type === 'floating') { const floatingGroup = this._floatingGroups.find( (_) => _.group === group @@ -1560,7 +1669,7 @@ export class DockviewComponent if (!options?.skipActive && this._activeGroup === group) { const groups = Array.from(this._groups.values()); - this.doSetGroupActive( + this.doSetGroupAndPanelActive( groups.length > 0 ? groups[0].value : undefined ); } @@ -1597,7 +1706,7 @@ export class DockviewComponent if (!options?.skipActive && this._activeGroup === group) { const groups = Array.from(this._groups.values()); - this.doSetGroupActive( + this.doSetGroupAndPanelActive( groups.length > 0 ? groups[0].value : undefined ); } @@ -1609,48 +1718,100 @@ export class DockviewComponent throw new Error('failed to find popout group'); } - return super.doRemoveGroup(group, options); + const re = super.doRemoveGroup(group, options); + + if (!options?.skipActive) { + if (this.activePanel !== activePanel) { + this._onDidActivePanelChange.fire(this.activePanel); + } + } + + return re; } - moveGroupOrPanel( - destinationGroup: DockviewGroupPanel, - sourceGroupId: string, - sourceItemId: string | undefined, - destinationTarget: Position, - destinationIndex?: number - ): void { + private _moving = false; + + movingLock(func: () => T): T { + const isMoving = this._moving; + + try { + this._moving = true; + return func(); + } finally { + this._moving = isMoving; + } + } + + moveGroupOrPanel(options: MoveGroupOrPanelOptions): void { + const destinationGroup = options.to.group; + const sourceGroupId = options.from.groupId; + const sourceItemId = options.from.panelId; + const destinationTarget = options.to.position; + const destinationIndex = options.to.index; + const sourceGroup = sourceGroupId ? this._groups.get(sourceGroupId)?.value : undefined; + if (!sourceGroup) { + throw new Error(`Failed to find group id ${sourceGroupId}`); + } + if (sourceItemId === undefined) { - if (sourceGroup) { - this.moveGroup( - sourceGroup, - destinationGroup, - destinationTarget - ); - } + /** + * Moving an entire group into another group + */ + + this.moveGroup({ + from: { group: sourceGroup }, + to: { + group: destinationGroup, + position: destinationTarget, + }, + }); return; } if (!destinationTarget || destinationTarget === 'center') { - const groupItem: IDockviewPanel | undefined = - sourceGroup?.model.removePanel(sourceItemId) ?? - this.panels.find((panel) => panel.id === sourceItemId); + /** + * Dropping a panel within another group + */ - if (!groupItem) { + const removedPanel: IDockviewPanel | undefined = this.movingLock( + () => + sourceGroup.model.removePanel(sourceItemId, { + skipEvents: true, + skipActive: true, + skipSetActiveGroup: true, + }) + ); + + if (!removedPanel) { throw new Error(`No panel with id ${sourceItemId}`); } - if (sourceGroup?.model.size === 0) { - this.doRemoveGroup(sourceGroup); + if (sourceGroup.model.size === 0) { + // remove the group and do not set a new group as active + this.doRemoveGroup(sourceGroup, { skipActive: true }); } - destinationGroup.model.openPanel(groupItem, { - index: destinationIndex, + this.movingLock(() => + destinationGroup.model.openPanel(removedPanel, { + index: destinationIndex, + skipSetGroupActive: true, + }) + ); + this.doSetGroupAndPanelActive(destinationGroup); + + this._onDidMovePanel.fire({ + panel: removedPanel, }); } else { + /** + * Dropping a panel to the extremities of a group which will place that panel + * into an adjacent group + */ + const referenceLocation = getGridLocation(destinationGroup.element); const targetLocation = getRelativeLocation( this.gridview.orientation, @@ -1658,7 +1819,12 @@ export class DockviewComponent destinationTarget ); - if (sourceGroup && sourceGroup.size < 2) { + if (sourceGroup.size < 2) { + /** + * If we are moving from a group which only has one panel left we will consider + * moving the group itself rather than moving the panel into a newly created group + */ + const [targetParentLocation, to] = tail(targetLocation); if (sourceGroup.api.location.type === 'grid') { @@ -1675,31 +1841,45 @@ export class DockviewComponent // if a group has one tab - we are essentially moving the 'group' // which is equivalent to swapping two views in this case this.gridview.moveView(sourceParentLocation, from, to); + return; } } // source group will become empty so delete the group - const targetGroup = this.doRemoveGroup(sourceGroup, { - skipActive: true, - skipDispose: true, - }); + const targetGroup = this.movingLock(() => + this.doRemoveGroup(sourceGroup, { + skipActive: true, + skipDispose: true, + }) + ); // after deleting the group we need to re-evaulate the ref location const updatedReferenceLocation = getGridLocation( destinationGroup.element ); + const location = getRelativeLocation( this.gridview.orientation, updatedReferenceLocation, destinationTarget ); - this.doAddGroup(targetGroup, location); + this.movingLock(() => this.doAddGroup(targetGroup, location)); + this.doSetGroupAndPanelActive(targetGroup); } else { - const groupItem: IDockviewPanel | undefined = - sourceGroup?.model.removePanel(sourceItemId) ?? - this.panels.find((panel) => panel.id === sourceItemId); + /** + * The group we are removing from has many panels, we need to remove the panels we are moving, + * create a new group, add the panels to that new group and add the new group in an appropiate position + */ + const removedPanel: IDockviewPanel | undefined = + this.movingLock(() => + sourceGroup.model.removePanel(sourceItemId, { + skipEvents: true, + skipActive: true, + skipSetActiveGroup: true, + }) + ); - if (!groupItem) { + if (!removedPanel) { throw new Error(`No panel with id ${sourceItemId}`); } @@ -1710,86 +1890,121 @@ export class DockviewComponent ); const group = this.createGroupAtLocation(dropLocation); - group.model.openPanel(groupItem); + this.movingLock(() => + group.model.openPanel(removedPanel, { + skipEvents: true, + skipSetGroupActive: true, + }) + ); + this.doSetGroupAndPanelActive(group); } } } - private moveGroup( - sourceGroup: DockviewGroupPanel, - referenceGroup: DockviewGroupPanel, - target: Position - ): void { - if (sourceGroup) { - if (!target || target === 'center') { - const activePanel = sourceGroup.activePanel; - const panels = [...sourceGroup.panels].map((p) => - sourceGroup.model.removePanel(p.id) - ); + moveGroup(options: MoveGroupOptions): void { + const from = options.from.group; + const to = options.to.group; + const target = options.to.position; - if (sourceGroup?.model.size === 0) { - this.doRemoveGroup(sourceGroup); - } + if (target === 'center') { + const activePanel = from.activePanel; + const panels = this.movingLock(() => + [...from.panels].map((p) => + from.model.removePanel(p.id, { + skipRender: true, + skipEvents: true, + }) + ) + ); + + if (from?.model.size === 0) { + this.doRemoveGroup(from, { skipActive: true }); + } + + this.movingLock(() => { for (const panel of panels) { - referenceGroup.model.openPanel(panel, { - skipSetPanelActive: panel !== activePanel, + to.model.openPanel(panel, { + skipRender: panel !== activePanel, + skipSetGroupActive: panel !== activePanel, }); } - } else { - switch (sourceGroup.api.location.type) { - case 'grid': - this.gridview.removeView( - getGridLocation(sourceGroup.element) - ); - break; - case 'floating': { - const selectedFloatingGroup = this._floatingGroups.find( - (x) => x.group === sourceGroup - ); - if (!selectedFloatingGroup) { - throw new Error('failed to find floating group'); - } - selectedFloatingGroup.dispose(); - break; - } - case 'popout': { - const selectedPopoutGroup = this._popoutGroups.find( - (x) => x.popoutGroup === sourceGroup - ); - if (!selectedPopoutGroup) { - throw new Error('failed to find popout group'); - } - selectedPopoutGroup.disposable.dispose(); + }); + + panels.forEach((panel) => { + this._onDidMovePanel.fire({ panel }); + }); + } else { + switch (from.api.location.type) { + case 'grid': + this.gridview.removeView(getGridLocation(from.element)); + break; + case 'floating': { + const selectedFloatingGroup = this._floatingGroups.find( + (x) => x.group === from + ); + if (!selectedFloatingGroup) { + throw new Error('failed to find floating group'); } + selectedFloatingGroup.dispose(); + break; + } + case 'popout': { + const selectedPopoutGroup = this._popoutGroups.find( + (x) => x.popoutGroup === from + ); + if (!selectedPopoutGroup) { + throw new Error('failed to find popout group'); + } + selectedPopoutGroup.disposable.dispose(); } - - const referenceLocation = getGridLocation( - referenceGroup.element - ); - const dropLocation = getRelativeLocation( - this.gridview.orientation, - referenceLocation, - target - ); - - this.gridview.addView( - sourceGroup, - Sizing.Distribute, - dropLocation - ); } + + const referenceLocation = getGridLocation(to.element); + const dropLocation = getRelativeLocation( + this.gridview.orientation, + referenceLocation, + target + ); + + this.gridview.addView(from, Sizing.Distribute, dropLocation); + + from.panels.forEach((panel) => { + this._onDidMovePanel.fire({ panel }); + }); } } - doSetGroupAndPanelActive( - group: DockviewGroupPanel | undefined, - ): void { - const activePanel = this.activePanel; + override doSetGroupActive(group: DockviewGroupPanel | undefined): void { super.doSetGroupActive(group); - if (this._activeGroup?.activePanel !== activePanel) { - this._onDidActivePanelChange.fire(this._activeGroup?.activePanel); + const activePanel = this.activePanel; + + if ( + !this._moving && + activePanel !== this._onDidActivePanelChange.value + ) { + this._onDidActivePanelChange.fire(activePanel); + } + } + + doSetGroupAndPanelActive(group: DockviewGroupPanel | undefined): void { + // if ( + // this.activeGroup === group && + // this._onDidActiveGroupChange.value !== group + // ) { + // this._onDidActiveGroupChange.fire(group); + // } + + super.doSetGroupActive(group); + + const activePanel = this.activePanel; + + if ( + !this._moving && + activePanel !== this._onDidActivePanelChange.value + ) { + this._onDidActivePanelChange.fire(activePanel); } } @@ -1836,7 +2051,14 @@ export class DockviewComponent }), view.model.onMove((event) => { const { groupId, itemId, target, index } = event; - this.moveGroupOrPanel(view, groupId, itemId, target, index); + this.moveGroupOrPanel({ + from: { groupId: groupId, panelId: itemId }, + to: { + group: view, + position: target, + index, + }, + }); }), view.model.onDidDrop((event) => { this._onDidDrop.fire(event); @@ -1853,13 +2075,27 @@ export class DockviewComponent this._onWillShowOverlay.fire(event); }), view.model.onDidAddPanel((event) => { + if (this._moving) { + return; + } this._onDidAddPanel.fire(event.panel); }), view.model.onDidRemovePanel((event) => { + if (this._moving) { + return; + } this._onDidRemovePanel.fire(event.panel); }), view.model.onDidActivePanelChange((event) => { - this._onDidActivePanelChange.fire(event.panel); + if (this._moving) { + return; + } + if (event.panel !== this.activePanel) { + return; + } + if (this._onDidActivePanelChange.value !== event.panel) { + this._onDidActivePanelChange.fire(event.panel); + } }) ); diff --git a/packages/dockview-core/src/dockview/dockviewGroupPanel.ts b/packages/dockview-core/src/dockview/dockviewGroupPanel.ts index 6163406f1..6f01fae5f 100644 --- a/packages/dockview-core/src/dockview/dockviewGroupPanel.ts +++ b/packages/dockview-core/src/dockview/dockviewGroupPanel.ts @@ -88,6 +88,13 @@ export class DockviewGroupPanel ); } + override focus(): void { + if (!this.api.isActive) { + this.api.setActive(); + } + super.focus(); + } + initialize(): void { this._model.initialize(); } diff --git a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts index 4ee7ac553..e54c15eeb 100644 --- a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts +++ b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts @@ -460,7 +460,7 @@ export class DockviewGroupPanelModel // must be run after the constructor otherwise this.parent may not be // correctly initialized - this.setActive(this.isActive, true, true); + this.setActive(this.isActive, true); this.updateContainer(); if (this.accessor.options.createRightHeaderActionsElement) { @@ -604,17 +604,25 @@ export class DockviewGroupPanelModel } focus(): void { - this._activePanel?.focus?.(); + this._activePanel?.focus(); } public openPanel( panel: IDockviewPanel, options: { index?: number; - skipSetPanelActive?: boolean; + skipRender?: boolean; + skipEvents?: boolean; skipSetGroupActive?: boolean; } = {} ): void { + /** + * set the panel group + * add the panel + * check if group active + * check if panel active + */ + if ( typeof options.index !== 'number' || options.index > this.panels.length @@ -622,34 +630,47 @@ export class DockviewGroupPanelModel options.index = this.panels.length; } - const skipSetPanelActive = !!options.skipSetPanelActive; - const skipSetGroupActive = !!options.skipSetGroupActive; + const skipRender = !!options.skipRender; // ensure the group is updated before we fire any events - panel.updateParentGroup(this.groupPanel, true); + panel.updateParentGroup(this.groupPanel, { isGroupActive: true }); + + this.doAddPanel(panel, options.index, { + skipRender, + }); if (this._activePanel === panel) { - if (!skipSetGroupActive) { - this.accessor.doSetGroupActive(this.groupPanel); - } this.contentContainer.renderPanel(panel, { asActive: true }); return; } - this.doAddPanel(panel, options.index, skipSetPanelActive); - - if (!skipSetPanelActive) { + if (!skipRender) { this.doSetActivePanel(panel); } - if (!skipSetGroupActive) { + if (!options.skipSetGroupActive) { this.accessor.doSetGroupActive(this.groupPanel); } - this.updateContainer(); + if (!options.skipEvents) { + panel.runEvents(); + this.updateContainer(); + } } - public removePanel(groupItemOrId: IDockviewPanel | string): IDockviewPanel { + public removePanel( + groupItemOrId: IDockviewPanel | string, + options: { + skipRender?: boolean; + skipEvents?: boolean; + skipActive?: boolean; + skipSetActiveGroup?: boolean; + } = { + skipRender: false, + skipEvents: false, + skipActive: false, + } + ): IDockviewPanel { const id = typeof groupItemOrId === 'string' ? groupItemOrId @@ -661,7 +682,7 @@ export class DockviewGroupPanelModel throw new Error('invalid operation'); } - return this._removePanel(panelToRemove); + return this._removePanel(panelToRemove, options); } public closeAllPanels(): void { @@ -692,15 +713,8 @@ export class DockviewGroupPanelModel this.tabsContainer.setRightActionsElement(element); } - public setActive( - isGroupActive: boolean, - skipFocus = false, - force = false - ): void { + public setActive(isGroupActive: boolean, force = false): void { if (!force && this.isActive === isGroupActive) { - if (!skipFocus) { - this._activePanel?.focus?.(); - } return; } @@ -716,12 +730,6 @@ export class DockviewGroupPanelModel } this.updateContainer(); - - if (isGroupActive) { - if (!skipFocus) { - this._activePanel?.focus?.(); - } - } } public layout(width: number, height: number): void { @@ -735,21 +743,35 @@ export class DockviewGroupPanelModel } } - private _removePanel(panel: IDockviewPanel): IDockviewPanel { + private _removePanel( + panel: IDockviewPanel, + options: { + skipRender?: boolean; + skipEvents?: boolean; + skipSetActiveGroup?: boolean; + } + ): IDockviewPanel { const isActivePanel = this._activePanel === panel; this.doRemovePanel(panel); if (isActivePanel && this.panels.length > 0) { const nextPanel = this.mostRecentlyUsed[0]; - this.openPanel(nextPanel); + this.openPanel(nextPanel, { + skipRender: options.skipRender, + skipEvents: options.skipEvents, + skipSetGroupActive: options.skipSetActiveGroup, + }); } if (this._activePanel && this.panels.length === 0) { this.doSetActivePanel(undefined); } - this.updateContainer(); + if (!options.skipEvents) { + this.updateContainer(); + } + return panel; } @@ -776,7 +798,9 @@ export class DockviewGroupPanelModel private doAddPanel( panel: IDockviewPanel, index: number = this.panels.length, - skipSetActive = false + options: { + skipRender: boolean; + } = { skipRender: false } ): void { const existingPanel = this._panels.indexOf(panel); const hasExistingPanel = existingPanel > -1; @@ -786,7 +810,7 @@ export class DockviewGroupPanelModel this.tabsContainer.openPanel(panel, index); - if (!skipSetActive) { + if (!options.skipRender) { this.contentContainer.openPanel(panel); } @@ -802,6 +826,10 @@ export class DockviewGroupPanelModel } private doSetActivePanel(panel: IDockviewPanel | undefined): void { + if (this._activePanel === panel) { + return; + } + this._activePanel = panel; if (panel) { @@ -811,7 +839,9 @@ export class DockviewGroupPanelModel this.updateMru(panel); - this._onDidActivePanelChange.fire({ panel }); + this._onDidActivePanelChange.fire({ + panel, + }); } } @@ -829,7 +859,10 @@ export class DockviewGroupPanelModel toggleClass(this.container, 'empty', this.isEmpty); this.panels.forEach((panel) => - panel.updateParentGroup(this.groupPanel, this.isActive) + // panel.updateParentGroup(this.groupPanel, { + // isGroupActive: this.isActive, + // }) + panel.runEvents() ); if (this.isEmpty && !this.watermark) { diff --git a/packages/dockview-core/src/dockview/dockviewPanel.ts b/packages/dockview-core/src/dockview/dockviewPanel.ts index 44921a173..2001207cf 100644 --- a/packages/dockview-core/src/dockview/dockviewPanel.ts +++ b/packages/dockview-core/src/dockview/dockviewPanel.ts @@ -18,11 +18,15 @@ export interface IDockviewPanel extends IDisposable, IPanel { readonly api: DockviewPanelApi; readonly title: string | undefined; readonly params: Parameters | undefined; - updateParentGroup(group: DockviewGroupPanel, isGroupActive: boolean): void; + updateParentGroup( + group: DockviewGroupPanel, + options: { isGroupActive: boolean } + ): void; init(params: IGroupPanelInitParameters): void; toJSON(): GroupviewPanelState; setTitle(title: string): void; update(event: PanelUpdateEvent): void; + runEvents(): void; } export class DockviewPanel @@ -94,16 +98,6 @@ export class DockviewPanel } focus(): void { - /** - * This is a progmatic request of focus - - * We need to tell the active panel that it can choose it's focus - * If the panel doesn't choose the panels container for it - */ - - if (!this.api.isActive) { - this.api.setActive(); - } - const event = new WillFocusEvent(); this.api._onWillFocus.fire(event); @@ -111,7 +105,9 @@ export class DockviewPanel return; } - this.group.model.focusContent(); + if (!this.api.isActive) { + this.api.setActive(); + } } public toJSON(): GroupviewPanelState { @@ -183,24 +179,49 @@ export class DockviewPanel public updateParentGroup( group: DockviewGroupPanel, - isGroupActive: boolean + options: { isGroupActive: boolean } ): void { this._group = group; - this.api.group = group; + // const isPanelVisible = this._group.model.isPanelActive(this); + + // const isActive = options.isGroupActive && isPanelVisible; + + // if (this.api.isActive !== isActive) { + // this.api._onDidActiveChange.fire({ + // isActive: options.isGroupActive && isPanelVisible, + // }); + // } + + // if (this.api.isVisible !== isPanelVisible) { + // this.api._onDidVisibilityChange.fire({ + // isVisible: isPanelVisible, + // }); + // } + + // this.view.updateParentGroup( + // this._group, + // this._group.model.isPanelActive(this) + // ); + } + + runEvents(): void { + this.api.group = this._group; const isPanelVisible = this._group.model.isPanelActive(this); - this.api._onDidActiveChange.fire({ - isActive: isGroupActive && isPanelVisible, - }); - this.api._onDidVisibilityChange.fire({ - isVisible: isPanelVisible, - }); + const isActive = this.group.api.isActive && isPanelVisible; - this.view.updateParentGroup( - this._group, - this._group.model.isPanelActive(this) - ); + if (this.api.isActive !== isActive) { + this.api._onDidActiveChange.fire({ + isActive: this.group.api.isActive && isPanelVisible, + }); + } + + if (this.api.isVisible !== isPanelVisible) { + this.api._onDidVisibilityChange.fire({ + isVisible: isPanelVisible, + }); + } } public layout(width: number, height: number): void { diff --git a/packages/dockview-core/src/dockview/dockviewPanelModel.ts b/packages/dockview-core/src/dockview/dockviewPanelModel.ts index b264ae663..50fe86eca 100644 --- a/packages/dockview-core/src/dockview/dockviewPanelModel.ts +++ b/packages/dockview-core/src/dockview/dockviewPanelModel.ts @@ -25,9 +25,6 @@ export class DockviewPanelModel implements IDockviewPanelModel { private readonly _content: IContentRenderer; private readonly _tab: ITabRenderer; - private _group: DockviewGroupPanel | null = null; - private _isPanelVisible: boolean | null = null; - get content(): IContentRenderer { return this._content; } @@ -52,28 +49,10 @@ export class DockviewPanelModel implements IDockviewPanelModel { } updateParentGroup( - group: DockviewGroupPanel, - isPanelVisible: boolean + _group: DockviewGroupPanel, + _isPanelVisible: boolean ): void { - if (group !== this._group) { - this._group = group; - if (this._content.onGroupChange) { - this._content.onGroupChange(group); - } - if (this._tab.onGroupChange) { - this._tab.onGroupChange(group); - } - } - - if (isPanelVisible !== this._isPanelVisible) { - this._isPanelVisible = isPanelVisible; - if (this._content.onPanelVisibleChange) { - this._content.onPanelVisibleChange(isPanelVisible); - } - if (this._tab.onPanelVisibleChange) { - this._tab.onPanelVisibleChange(isPanelVisible); - } - } + // noop } layout(width: number, height: number): void { diff --git a/packages/dockview-core/src/dockview/types.ts b/packages/dockview-core/src/dockview/types.ts index 0059a3952..581d46ee8 100644 --- a/packages/dockview-core/src/dockview/types.ts +++ b/packages/dockview-core/src/dockview/types.ts @@ -47,8 +47,6 @@ export interface ITabRenderer > { readonly element: HTMLElement; init(parameters: GroupPanelPartInitParameters): void; - onGroupChange?(group: DockviewGroupPanel): void; - onPanelVisibleChange?(isPanelVisible: boolean): void; } export interface IContentRenderer @@ -60,8 +58,6 @@ export interface IContentRenderer readonly onDidFocus?: Event; readonly onDidBlur?: Event; init(parameters: GroupPanelContentPartInitParameters): void; - onGroupChange?(group: DockviewGroupPanel): void; - onPanelVisibleChange?(isPanelVisible: boolean): void; } // watermark component diff --git a/packages/dockview-core/src/events.ts b/packages/dockview-core/src/events.ts index 27c515460..28a1bfd5f 100644 --- a/packages/dockview-core/src/events.ts +++ b/packages/dockview-core/src/events.ts @@ -93,6 +93,10 @@ export class Emitter implements IDisposable { Emitter.ENABLE_TRACKING = isEnabled; } + get value(): T | undefined { + return this._last; + } + constructor(private readonly options?: EmitterOptions) {} get event(): Event { diff --git a/packages/dockview-core/src/gridview/baseComponentGridview.ts b/packages/dockview-core/src/gridview/baseComponentGridview.ts index 1fe4f909d..500e5dd8a 100644 --- a/packages/dockview-core/src/gridview/baseComponentGridview.ts +++ b/packages/dockview-core/src/gridview/baseComponentGridview.ts @@ -54,10 +54,6 @@ export interface IBaseGrid { readonly activeGroup: T | undefined; readonly size: number; readonly groups: T[]; - readonly onDidLayoutChange: Event; - readonly onDidRemoveGroup: Event; - readonly onDidAddGroup: Event; - readonly onDidActiveGroupChange: Event; getPanel(id: string): T | undefined; toJSON(): object; fromJSON(data: any): void; @@ -70,6 +66,7 @@ export interface IBaseGrid { exitMaximizedGroup(): void; hasMaximizedGroup(): boolean; readonly onDidMaxmizedGroupChange: Event; + readonly onDidLayoutChange: Event; } export abstract class BaseGrid @@ -85,15 +82,15 @@ export abstract class BaseGrid private _onDidLayoutChange = new Emitter(); readonly onDidLayoutChange = this._onDidLayoutChange.event; - protected readonly _onDidRemoveGroup = new Emitter(); - readonly onDidRemoveGroup: Event = this._onDidRemoveGroup.event; + private readonly _onDidRemove = new Emitter(); + readonly onDidRemove: Event = this._onDidRemove.event; - protected readonly _onDidAddGroup = new Emitter(); - readonly onDidAddGroup: Event = this._onDidAddGroup.event; + private readonly _onDidAdd = new Emitter(); + readonly onDidAdd: Event = this._onDidAdd.event; - private readonly _onDidActiveGroupChange = new Emitter(); - readonly onDidActiveGroupChange: Event = - this._onDidActiveGroupChange.event; + private readonly _onDidActiveChange = new Emitter(); + readonly onDidActiveChange: Event = + this._onDidActiveChange.event; protected readonly _bufferOnDidLayoutChange = new TickDelayedEvent(); @@ -167,9 +164,9 @@ export abstract class BaseGrid this._bufferOnDidLayoutChange.fire(); }), Event.any( - this.onDidAddGroup, - this.onDidRemoveGroup, - this.onDidActiveGroupChange + this.onDidAdd, + this.onDidRemove, + this.onDidActiveChange )(() => { this._bufferOnDidLayoutChange.fire(); }), @@ -222,9 +219,9 @@ export abstract class BaseGrid ): void { this.gridview.addView(group, size ?? Sizing.Distribute, location); - this._onDidAddGroup.fire(group); + this._onDidAdd.fire(group); - this.doSetGroupActive(group); + // this.doSetGroupActive(group); } protected doRemoveGroup( @@ -243,10 +240,9 @@ export abstract class BaseGrid item.disposable.dispose(); item.value.dispose(); this._groups.delete(group.id); + this._onDidRemove.fire(group); } - this._onDidRemoveGroup.fire(group); - if (!options?.skipActive && this._activeGroup === group) { const groups = Array.from(this._groups.values()); @@ -276,7 +272,7 @@ export abstract class BaseGrid this._activeGroup = group; - this._onDidActiveGroupChange.fire(group); + this._onDidActiveChange.fire(group); } public removeGroup(group: T): void { @@ -330,9 +326,9 @@ export abstract class BaseGrid } public dispose(): void { - this._onDidActiveGroupChange.dispose(); - this._onDidAddGroup.dispose(); - this._onDidRemoveGroup.dispose(); + this._onDidActiveChange.dispose(); + this._onDidAdd.dispose(); + this._onDidRemove.dispose(); this._onDidLayoutChange.dispose(); for (const group of this.groups) { diff --git a/packages/dockview-core/src/gridview/gridviewComponent.ts b/packages/dockview-core/src/gridview/gridviewComponent.ts index d1e4a09b5..e01159821 100644 --- a/packages/dockview-core/src/gridview/gridviewComponent.ts +++ b/packages/dockview-core/src/gridview/gridviewComponent.ts @@ -71,6 +71,9 @@ export interface IGridviewComponent extends IBaseGrid { ): void; setVisible(panel: IGridviewPanel, visible: boolean): void; setActive(panel: IGridviewPanel): void; + readonly onDidRemoveGroup: Event; + readonly onDidAddGroup: Event; + readonly onDidActiveGroupChange: Event; } export class GridviewComponent @@ -83,6 +86,19 @@ export class GridviewComponent private readonly _onDidLayoutfromJSON = new Emitter(); readonly onDidLayoutFromJSON: Event = this._onDidLayoutfromJSON.event; + private readonly _onDidRemoveGroup = new Emitter(); + readonly onDidRemoveGroup: Event = + this._onDidRemoveGroup.event; + + protected readonly _onDidAddGroup = new Emitter(); + readonly onDidAddGroup: Event = this._onDidAddGroup.event; + + private readonly _onDidActiveGroupChange = new Emitter< + GridviewPanel | undefined + >(); + readonly onDidActiveGroupChange: Event = + this._onDidActiveGroupChange.event; + get orientation(): Orientation { return this.gridview.orientation; } @@ -114,6 +130,21 @@ export class GridviewComponent this._options = options; + this.addDisposables( + this._onDidAddGroup, + this._onDidRemoveGroup, + this._onDidActiveGroupChange, + this.onDidAdd((event) => { + this._onDidAddGroup.fire(event); + }), + this.onDidRemove((event) => { + this._onDidRemoveGroup.fire(event); + }), + this.onDidActiveChange((event) => { + this._onDidActiveGroupChange.fire(event); + }) + ); + if (!this.options.components) { this.options.components = {}; } @@ -364,6 +395,7 @@ export class GridviewComponent this.registerPanel(view); this.doAddGroup(view, relativeLocation, options.size); + this.doSetGroupActive(view); return view; } diff --git a/packages/docs/docs/components/dockview.mdx b/packages/docs/docs/components/dockview.mdx index 40f020fb0..6b7239ae2 100644 --- a/packages/docs/docs/components/dockview.mdx +++ b/packages/docs/docs/components/dockview.mdx @@ -33,6 +33,8 @@ import DockviewMaximizeGroup from '@site/sandboxes/maximizegroup-dockview/src/ap import DockviewRenderMode from '@site/sandboxes/rendermode-dockview/src/app'; import DockviewScrollbars from '@site/sandboxes/scrollbars-dockview/src/app'; +import DockviewFocus from '@site/sandboxes/focus-dockview/src/app'; + import { DocRef } from '@site/src/components/ui/reference/docRef'; import { attach as attachDockviewVanilla } from '@site/sandboxes/javascript/vanilla-dockview/src/app'; @@ -42,6 +44,11 @@ import { attach as attachNativeDockview } from '@site/sandboxes/javascript/fullw # Dockview + + ## Introduction Dockview is an abstraction built on top of [Gridviews](./gridview) where each view is a container of many tabbed panels. diff --git a/packages/docs/sandboxes/demo-dockview/src/app.tsx b/packages/docs/sandboxes/demo-dockview/src/app.tsx index 975f3f6be..1cdc78e24 100644 --- a/packages/docs/sandboxes/demo-dockview/src/app.tsx +++ b/packages/docs/sandboxes/demo-dockview/src/app.tsx @@ -5,14 +5,169 @@ import { IDockviewPanelHeaderProps, IDockviewPanelProps, IDockviewHeaderActionsProps, + DockviewPanelApi, + DockviewPanelRenderer, + DockviewGroupLocation, + DockviewApi, } from 'dockview'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { v4 } from 'uuid'; import './app.scss'; +interface PanelApiMetadata { + isActive: { + value: boolean; + count: number; + }; + isVisible: { + value: boolean; + count: number; + }; + isHidden: { + value: boolean; + count: number; + }; + renderer: { + value: DockviewPanelRenderer; + count: number; + }; + isGroupActive: { + value: boolean; + count: number; + }; + groupChanged: { + count: number; + }; + location: { + value: DockviewGroupLocation; + count: number; + }; + didFocus: { + count: number; + }; + dimensions: { + count: number; + height: number; + width: number; + }; +} + +function usePanelApiMetadata(api: DockviewPanelApi): PanelApiMetadata { + const [state, setState] = React.useState({ + isActive: { value: api.isActive, count: 0 }, + isVisible: { value: api.isVisible, count: 0 }, + isHidden: { value: api.isHidden, count: 0 }, + renderer: { value: api.renderer, count: 0 }, + isGroupActive: { value: api.isGroupActive, count: 0 }, + groupChanged: { count: 0 }, + location: { value: api.location, count: 0 }, + didFocus: { count: 0 }, + dimensions: { count: 0, height: api.height, width: api.width }, + }); + + React.useEffect(() => { + const d1 = api.onDidActiveChange((event) => { + setState((_) => ({ + ..._, + isActive: { + value: event.isActive, + count: _.isActive.count + 1, + }, + })); + }); + const d2 = api.onDidActiveGroupChange((event) => { + setState((_) => ({ + ..._, + isGroupActive: { + value: event.isActive, + count: _.isGroupActive.count + 1, + }, + })); + }); + const d3 = api.onDidDimensionsChange((event) => { + setState((_) => ({ + ..._, + dimensions: { + count: _.dimensions.count + 1, + height: event.height, + width: event.width, + }, + })); + }); + const d4 = api.onDidFocusChange((event) => { + setState((_) => ({ + ..._, + didFocus: { + count: _.didFocus.count + 1, + }, + })); + }); + const d5 = api.onDidGroupChange((event) => { + setState((_) => ({ + ..._, + groupChanged: { + count: _.groupChanged.count + 1, + }, + })); + }); + const d6 = api.onDidHiddenChange((event) => { + setState((_) => ({ + ..._, + isHidden: { + value: event.isHidden, + count: _.isHidden.count + 1, + }, + })); + }); + const d7 = api.onDidLocationChange((event) => { + setState((_) => ({ + ..._, + location: { + value: event.location, + count: _.location.count + 1, + }, + })); + }); + const d8 = api.onDidRendererChange((event) => { + setState((_) => ({ + ..._, + renderer: { + value: event.renderer, + count: _.renderer.count + 1, + }, + })); + }); + const d9 = api.onDidVisibilityChange((event) => { + setState((_) => ({ + ..._, + isVisible: { + value: event.isVisible, + count: _.isVisible.count + 1, + }, + })); + }); + + return () => { + d1.dispose(); + d2.dispose(); + d3.dispose(); + d4.dispose(); + d5.dispose(); + d6.dispose(); + d7.dispose(); + d8.dispose(); + d9.dispose(); + }; + }, [api]); + + return state; +} + const components = { - default: (props: IDockviewPanelProps<{ title: string }>) => { + default: (props: IDockviewPanelProps) => { + const metadata = usePanelApiMetadata(props.api); + return (
+
+                    {JSON.stringify(metadata, null, 4)}
+                
{ }; const DockviewDemo = (props: { theme?: string }) => { + const [logLines, setLogLines] = React.useState([]); + + const [panels, setPanels] = React.useState([]); + const [groups, setGroups] = React.useState([]); + const [api, setApi] = React.useState(); + + const [activePanel, setActivePanel] = React.useState(); + const [activeGroup, setActiveGroup] = React.useState(); + const onReady = (event: DockviewReadyEvent) => { + setApi(event.api); + + event.api.onDidAddPanel((event) => { + setPanels((_) => [..._, event.id]); + setLogLines((line) => [`Panel Added ${event.id}`, ...line]); + }); + event.api.onDidActivePanelChange((event) => { + setActivePanel(event?.id); + setLogLines((line) => [`Panel Activated ${event?.id}`, ...line]); + }); + event.api.onDidRemovePanel((event) => { + setPanels((_) => { + const next = [..._]; + next.splice( + next.findIndex((x) => x === event.id), + 1 + ); + + return next; + }); + setLogLines((line) => [`Panel Removed ${event.id}`, ...line]); + }); + + event.api.onDidAddGroup((event) => { + setGroups((_) => [..._, event.id]); + setLogLines((line) => [`Group Added ${event.id}`, ...line]); + }); + + event.api.onDidRemoveGroup((event) => { + setGroups((_) => { + const next = [..._]; + next.splice( + next.findIndex((x) => x === event.id), + 1 + ); + + return next; + }); + setLogLines((line) => [`Group Removed ${event.id}`, ...line]); + }); + + event.api.onDidActiveGroupChange((event) => { + setActiveGroup(event?.id); + setLogLines((line) => [`Group Activated ${event?.id}`, ...line]); + }); + const panel1 = event.api.addPanel({ id: 'panel_1', component: 'default', @@ -257,16 +470,179 @@ const DockviewDemo = (props: { theme?: string }) => { panel1.api.setActive(); }; + const onAddPanel = () => { + api?.addPanel({ + id: `id_${Date.now().toString()}`, + component: 'default', + title: `Tab ${counter++}`, + }); + }; + + const onAddGroup = () => { + api?.addGroup(); + }; + return ( - +
+
+
+ + +
+
+ {panels.map((x) => { + const onClick = () => { + api?.getPanel(x)?.focus(); + }; + return ( + <> + + + + + ); + })} +
+
+ {groups.map((x) => { + const onClick = () => { + api?.getGroup(x)?.focus(); + }; + return ( + <> + + + + + ); + })} +
+
+
+ +
+
+ {logLines.map((line, i) => { + return ( +
+ + {logLines.length - i} + + {line} +
+ ); + })} +
+
); }; diff --git a/packages/docs/sandboxes/focus-dockview/package.json b/packages/docs/sandboxes/focus-dockview/package.json new file mode 100644 index 000000000..37a2bc4b9 --- /dev/null +++ b/packages/docs/sandboxes/focus-dockview/package.json @@ -0,0 +1,32 @@ +{ + "name": "focus-dockview", + "description": "", + "keywords": [ + "dockview" + ], + "version": "1.0.0", + "main": "src/index.tsx", + "dependencies": { + "dockview": "*", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.0.28", + "@types/react-dom": "^18.0.11", + "typescript": "^4.9.5", + "react-scripts": "*" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + }, + "browserslist": [ + ">0.2%", + "not dead", + "not ie <= 11", + "not op_mini all" + ] +} diff --git a/packages/docs/sandboxes/focus-dockview/public/index.html b/packages/docs/sandboxes/focus-dockview/public/index.html new file mode 100644 index 000000000..1f8a52426 --- /dev/null +++ b/packages/docs/sandboxes/focus-dockview/public/index.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + + React App + + + + +
+ + + + diff --git a/packages/docs/sandboxes/focus-dockview/src/app.tsx b/packages/docs/sandboxes/focus-dockview/src/app.tsx new file mode 100644 index 000000000..5c537db02 --- /dev/null +++ b/packages/docs/sandboxes/focus-dockview/src/app.tsx @@ -0,0 +1,125 @@ +import { + DockviewApi, + DockviewReact, + DockviewReadyEvent, + IDockviewPanelProps, +} from 'dockview'; +import * as React from 'react'; + +const components = { + default: (props: IDockviewPanelProps) => { + React.useEffect(() => { + const d1 = props.api.onWillFocus((event) => { + console.log('willFocus'); + }); + + const d2 = props.api.onDidActiveChange((event) => { + console.log(props.api.title, event, 'active'); + }); + + const d3 = props.api.onDidActiveGroupChange((event) => { + console.log( + props.api.title, + props.api.group.api.isActive, + 'active-group' + ); + }); + + const d4 = props.api.onDidGroupChange((event) => { + console.log( + props.api.title, + props.api.group.id, + 'group-change' + ); + }); + + return () => { + d1.dispose(); + d2.dispose(); + d3.dispose(); + }; + }, [props.api]); + + return ( +
+ {props.api.title} +
+ ); + }, +}; + +export const App: React.FC = (props: { theme?: string }) => { + const [api, setApi] = React.useState(); + + const onReady = (event: DockviewReadyEvent) => { + setApi(event.api); + + event.api.addPanel({ + id: 'panel_1', + title: 'Panel 1', + component: 'default', + }); + + event.api.addPanel({ + id: 'panel_2', + title: 'Panel 2', + component: 'default', + }); + + // event.api.onDidAddPanel((event) => { + // console.log('add panel', event); + // }); + // event.api.onDidActivePanelChange((event) => { + // console.log('active panel', event); + // }); + // event.api.onDidRemovePanel((event) => { + // console.log('remove panel', event); + // }); + }; + + return ( +
+
+ + + + +
+
+ +
+
+ ); +}; + +export default App; diff --git a/packages/docs/sandboxes/focus-dockview/src/index.tsx b/packages/docs/sandboxes/focus-dockview/src/index.tsx new file mode 100644 index 000000000..2fe1be232 --- /dev/null +++ b/packages/docs/sandboxes/focus-dockview/src/index.tsx @@ -0,0 +1,20 @@ +import { StrictMode } from 'react'; +import * as ReactDOMClient from 'react-dom/client'; +import './styles.css'; +import 'dockview/dist/styles/dockview.css'; + +import App from './app'; + +const rootElement = document.getElementById('root'); + +if (rootElement) { + const root = ReactDOMClient.createRoot(rootElement); + + root.render( + +
+ +
+
+ ); +} diff --git a/packages/docs/sandboxes/focus-dockview/src/styles.css b/packages/docs/sandboxes/focus-dockview/src/styles.css new file mode 100644 index 000000000..92b6a1b36 --- /dev/null +++ b/packages/docs/sandboxes/focus-dockview/src/styles.css @@ -0,0 +1,16 @@ +body { + margin: 0px; + color: white; + font-family: sans-serif; + text-align: center; +} + +#root { + height: 100vh; + width: 100vw; +} + +.app { + height: 100%; + +} diff --git a/packages/docs/sandboxes/focus-dockview/tsconfig.json b/packages/docs/sandboxes/focus-dockview/tsconfig.json new file mode 100644 index 000000000..cdc4fb5f5 --- /dev/null +++ b/packages/docs/sandboxes/focus-dockview/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "outDir": "build/dist", + "module": "esnext", + "target": "es5", + "lib": ["es6", "dom"], + "sourceMap": true, + "allowJs": true, + "jsx": "react-jsx", + "moduleResolution": "node", + "rootDir": "src", + "forceConsistentCasingInFileNames": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitAny": true, + "strictNullChecks": true + } +} From 284fb1440b256855b74cdbdded69a56e4dc5217e Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Thu, 8 Feb 2024 19:38:52 +0000 Subject: [PATCH 3/3] feat: events cleanup --- .../dockview/dockviewGroupPanelModel.spec.ts | 7 +-- .../paneview/paneviewComponent.spec.ts | 4 +- .../splitview/splitviewComponent.spec.ts | 8 +++- .../dockview-core/src/api/gridviewPanelApi.ts | 4 +- packages/dockview-core/src/api/panelApi.ts | 16 ++----- .../src/dockview/dockviewComponent.ts | 16 +++---- .../src/dockview/dockviewGroupPanelModel.ts | 45 +++++++------------ .../src/dockview/dockviewPanel.ts | 40 ++++++++--------- 8 files changed, 57 insertions(+), 83 deletions(-) diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts index 5e523f06b..b1963dbe5 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewGroupPanelModel.spec.ts @@ -196,10 +196,7 @@ export class TestPanel implements IDockviewPanel { this._params = params; } - updateParentGroup( - group: DockviewGroupPanel, - options: { isGroupActive: boolean } - ): void { + updateParentGroup(group: DockviewGroupPanel): void { // } @@ -631,7 +628,7 @@ describe('dockviewGroupPanelModel', () => { renderer: 'onlyWhenVisibile', } as any); - cut.openPanel(panel3, { skipRender: true }); + cut.openPanel(panel3, { skipSetActive: true }); expect(contentContainer.length).toBe(1); expect(contentContainer.item(0)).toBe(panel2.view.content.element); diff --git a/packages/dockview-core/src/__tests__/paneview/paneviewComponent.spec.ts b/packages/dockview-core/src/__tests__/paneview/paneviewComponent.spec.ts index fac932e51..b94b5123d 100644 --- a/packages/dockview-core/src/__tests__/paneview/paneviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/paneview/paneviewComponent.spec.ts @@ -78,7 +78,7 @@ describe('componentPaneview', () => { }, }); - paneview.layout(600, 400); + paneview.layout(300, 200); paneview.addPanel({ id: 'panel1', @@ -108,6 +108,8 @@ describe('componentPaneview', () => { }) ); + paneview.layout(600, 400); + expect(panel1Dimensions).toEqual({ width: 600, height: 22 }); expect(panel2Dimensions).toEqual({ width: 600, height: 22 }); diff --git a/packages/dockview-core/src/__tests__/splitview/splitviewComponent.spec.ts b/packages/dockview-core/src/__tests__/splitview/splitviewComponent.spec.ts index d42f37504..43cc6ce08 100644 --- a/packages/dockview-core/src/__tests__/splitview/splitviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/splitview/splitviewComponent.spec.ts @@ -233,7 +233,7 @@ describe('componentSplitview', () => { }, }); - splitview.layout(600, 400); + splitview.layout(300, 200); splitview.addPanel({ id: 'panel1', component: 'testPanel' }); splitview.addPanel({ id: 'panel2', component: 'testPanel' }); @@ -255,6 +255,8 @@ describe('componentSplitview', () => { }) ); + splitview.layout(600, 400); + expect(panel1Dimensions).toEqual({ width: 600, height: 200 }); expect(panel2Dimensions).toEqual({ width: 600, height: 200 }); @@ -283,7 +285,7 @@ describe('componentSplitview', () => { }, }); - splitview.layout(600, 400); + splitview.layout(300, 200); splitview.addPanel({ id: 'panel1', component: 'testPanel' }); splitview.addPanel({ id: 'panel2', component: 'testPanel' }); @@ -305,6 +307,8 @@ describe('componentSplitview', () => { }) ); + splitview.layout(600, 400); + expect(panel1Dimensions).toEqual({ width: 300, height: 400 }); expect(panel2Dimensions).toEqual({ width: 300, height: 400 }); diff --git a/packages/dockview-core/src/api/gridviewPanelApi.ts b/packages/dockview-core/src/api/gridviewPanelApi.ts index 69bd6cdbd..4a7e50424 100644 --- a/packages/dockview-core/src/api/gridviewPanelApi.ts +++ b/packages/dockview-core/src/api/gridviewPanelApi.ts @@ -37,9 +37,7 @@ export class GridviewPanelApiImpl readonly onDidConstraintsChangeInternal: Event = this._onDidConstraintsChangeInternal.event; - readonly _onDidConstraintsChange = new Emitter({ - replay: true, - }); + readonly _onDidConstraintsChange = new Emitter(); readonly onDidConstraintsChange: Event = this._onDidConstraintsChange.event; diff --git a/packages/dockview-core/src/api/panelApi.ts b/packages/dockview-core/src/api/panelApi.ts index b08e9289c..d604f0122 100644 --- a/packages/dockview-core/src/api/panelApi.ts +++ b/packages/dockview-core/src/api/panelApi.ts @@ -82,22 +82,16 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi { private readonly panelUpdatesDisposable = new MutableDisposable(); - readonly _onDidDimensionChange = new Emitter({ - replay: true, - }); + readonly _onDidDimensionChange = new Emitter(); readonly onDidDimensionsChange = this._onDidDimensionChange.event; - readonly _onDidChangeFocus = new Emitter({ - replay: true, - }); + readonly _onDidChangeFocus = new Emitter(); readonly onDidFocusChange: Event = this._onDidChangeFocus.event; // readonly _onWillFocus = new Emitter(); readonly onWillFocus: Event = this._onWillFocus.event; // - readonly _onDidVisibilityChange = new Emitter({ - replay: true, - }); + readonly _onDidVisibilityChange = new Emitter(); readonly onDidVisibilityChange: Event = this._onDidVisibilityChange.event; @@ -105,9 +99,7 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi { readonly onDidHiddenChange: Event = this._onDidHiddenChange.event; - readonly _onDidActiveChange = new Emitter({ - replay: true, - }); + readonly _onDidActiveChange = new Emitter(); readonly onDidActiveChange: Event = this._onDidActiveChange.event; diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index b782a76ab..ac24f2001 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -632,7 +632,7 @@ export class DockviewComponent panels.forEach((panel) => { options.to.model.openPanel(panel, { - skipRender: activePanel !== panel, + skipSetActive: activePanel !== panel, }); }); } @@ -1207,7 +1207,7 @@ export class DockviewComponent activeView === panel.id; group.model.openPanel(panel, { - skipRender: !isActive, + skipSetActive: !isActive, skipSetGroupActive: true, }); } @@ -1780,8 +1780,7 @@ export class DockviewComponent const removedPanel: IDockviewPanel | undefined = this.movingLock( () => sourceGroup.model.removePanel(sourceItemId, { - skipEvents: true, - skipActive: true, + skipSetActive: true, skipSetActiveGroup: true, }) ); @@ -1873,8 +1872,7 @@ export class DockviewComponent const removedPanel: IDockviewPanel | undefined = this.movingLock(() => sourceGroup.model.removePanel(sourceItemId, { - skipEvents: true, - skipActive: true, + skipSetActive: true, skipSetActiveGroup: true, }) ); @@ -1892,7 +1890,6 @@ export class DockviewComponent const group = this.createGroupAtLocation(dropLocation); this.movingLock(() => group.model.openPanel(removedPanel, { - skipEvents: true, skipSetGroupActive: true, }) ); @@ -1912,8 +1909,7 @@ export class DockviewComponent const panels = this.movingLock(() => [...from.panels].map((p) => from.model.removePanel(p.id, { - skipRender: true, - skipEvents: true, + skipSetActive: true, }) ) ); @@ -1925,7 +1921,7 @@ export class DockviewComponent this.movingLock(() => { for (const panel of panels) { to.model.openPanel(panel, { - skipRender: panel !== activePanel, + skipSetActive: panel !== activePanel, skipSetGroupActive: panel !== activePanel, }); } diff --git a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts index e54c15eeb..951c9e983 100644 --- a/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts +++ b/packages/dockview-core/src/dockview/dockviewGroupPanelModel.ts @@ -611,8 +611,7 @@ export class DockviewGroupPanelModel panel: IDockviewPanel, options: { index?: number; - skipRender?: boolean; - skipEvents?: boolean; + skipSetActive?: boolean; skipSetGroupActive?: boolean; } = {} ): void { @@ -630,13 +629,15 @@ export class DockviewGroupPanelModel options.index = this.panels.length; } - const skipRender = !!options.skipRender; + const skipSetActive = !!options.skipSetActive; // ensure the group is updated before we fire any events - panel.updateParentGroup(this.groupPanel, { isGroupActive: true }); + panel.updateParentGroup(this.groupPanel, { + skipSetActive: options.skipSetActive, + }); this.doAddPanel(panel, options.index, { - skipRender, + skipSetActive: skipSetActive, }); if (this._activePanel === panel) { @@ -644,7 +645,7 @@ export class DockviewGroupPanelModel return; } - if (!skipRender) { + if (!skipSetActive) { this.doSetActivePanel(panel); } @@ -652,8 +653,7 @@ export class DockviewGroupPanelModel this.accessor.doSetGroupActive(this.groupPanel); } - if (!options.skipEvents) { - panel.runEvents(); + if (!options.skipSetActive) { this.updateContainer(); } } @@ -661,14 +661,10 @@ export class DockviewGroupPanelModel public removePanel( groupItemOrId: IDockviewPanel | string, options: { - skipRender?: boolean; - skipEvents?: boolean; - skipActive?: boolean; + skipSetActive?: boolean; skipSetActiveGroup?: boolean; } = { - skipRender: false, - skipEvents: false, - skipActive: false, + skipSetActive: false, } ): IDockviewPanel { const id = @@ -746,8 +742,7 @@ export class DockviewGroupPanelModel private _removePanel( panel: IDockviewPanel, options: { - skipRender?: boolean; - skipEvents?: boolean; + skipSetActive?: boolean; skipSetActiveGroup?: boolean; } ): IDockviewPanel { @@ -758,8 +753,7 @@ export class DockviewGroupPanelModel if (isActivePanel && this.panels.length > 0) { const nextPanel = this.mostRecentlyUsed[0]; this.openPanel(nextPanel, { - skipRender: options.skipRender, - skipEvents: options.skipEvents, + skipSetActive: options.skipSetActive, skipSetGroupActive: options.skipSetActiveGroup, }); } @@ -768,7 +762,7 @@ export class DockviewGroupPanelModel this.doSetActivePanel(undefined); } - if (!options.skipEvents) { + if (!options.skipSetActive) { this.updateContainer(); } @@ -799,8 +793,8 @@ export class DockviewGroupPanelModel panel: IDockviewPanel, index: number = this.panels.length, options: { - skipRender: boolean; - } = { skipRender: false } + skipSetActive: boolean; + } = { skipSetActive: false } ): void { const existingPanel = this._panels.indexOf(panel); const hasExistingPanel = existingPanel > -1; @@ -810,7 +804,7 @@ export class DockviewGroupPanelModel this.tabsContainer.openPanel(panel, index); - if (!options.skipRender) { + if (!options.skipSetActive) { this.contentContainer.openPanel(panel); } @@ -858,12 +852,7 @@ export class DockviewGroupPanelModel private updateContainer(): void { toggleClass(this.container, 'empty', this.isEmpty); - this.panels.forEach((panel) => - // panel.updateParentGroup(this.groupPanel, { - // isGroupActive: this.isActive, - // }) - panel.runEvents() - ); + this.panels.forEach((panel) => panel.runEvents()); if (this.isEmpty && !this.watermark) { const watermark = this.accessor.createWatermarkComponent(); diff --git a/packages/dockview-core/src/dockview/dockviewPanel.ts b/packages/dockview-core/src/dockview/dockviewPanel.ts index 2001207cf..72ae1ca72 100644 --- a/packages/dockview-core/src/dockview/dockviewPanel.ts +++ b/packages/dockview-core/src/dockview/dockviewPanel.ts @@ -20,7 +20,7 @@ export interface IDockviewPanel extends IDisposable, IPanel { readonly params: Parameters | undefined; updateParentGroup( group: DockviewGroupPanel, - options: { isGroupActive: boolean } + options?: { skipSetActive?: boolean } ): void; init(params: IGroupPanelInitParameters): void; toJSON(): GroupviewPanelState; @@ -179,34 +179,30 @@ export class DockviewPanel public updateParentGroup( group: DockviewGroupPanel, - options: { isGroupActive: boolean } + options?: { skipSetActive?: boolean } ): void { this._group = group; + this.api.group = this._group; - // const isPanelVisible = this._group.model.isPanelActive(this); + const isPanelVisible = this._group.model.isPanelActive(this); + const isActive = this.group.api.isActive && isPanelVisible; - // const isActive = options.isGroupActive && isPanelVisible; + if (!options?.skipSetActive) { + if (this.api.isActive !== isActive) { + this.api._onDidActiveChange.fire({ + isActive: this.group.api.isActive && isPanelVisible, + }); + } + } - // if (this.api.isActive !== isActive) { - // this.api._onDidActiveChange.fire({ - // isActive: options.isGroupActive && isPanelVisible, - // }); - // } - - // if (this.api.isVisible !== isPanelVisible) { - // this.api._onDidVisibilityChange.fire({ - // isVisible: isPanelVisible, - // }); - // } - - // this.view.updateParentGroup( - // this._group, - // this._group.model.isPanelActive(this) - // ); + if (this.api.isVisible !== isPanelVisible) { + this.api._onDidVisibilityChange.fire({ + isVisible: isPanelVisible, + }); + } } runEvents(): void { - this.api.group = this._group; const isPanelVisible = this._group.model.isPanelActive(this); const isActive = this.group.api.isActive && isPanelVisible; @@ -225,7 +221,7 @@ export class DockviewPanel } public layout(width: number, height: number): void { - // the obtain the correct dimensions of the content panel we must deduct the tab height + // TODO: Can we somehow do height without header height or indicate what the header height is? this.api._onDidDimensionChange.fire({ width, height: height,