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] 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 + } +}