From c8ea368e88874518753346efb14b1821f9e4a389 Mon Sep 17 00:00:00 2001 From: Mathias Borglin Date: Wed, 7 May 2025 13:16:39 +0200 Subject: [PATCH 1/6] Add onDidBlockPopout event --- packages/dockview-core/src/api/component.api.ts | 4 ++++ packages/dockview-core/src/dockview/dockviewComponent.ts | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/dockview-core/src/api/component.api.ts b/packages/dockview-core/src/api/component.api.ts index 13d108811..34bade162 100644 --- a/packages/dockview-core/src/api/component.api.ts +++ b/packages/dockview-core/src/api/component.api.ts @@ -749,6 +749,10 @@ export class DockviewApi implements CommonApi { return this.component.onDidPopoutGroupPositionChange; } + get onDidBlockPopout(): Event { + return this.component.onDidBlockPopout; + } + /** * All panel objects. */ diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 707d4f7e9..ff1284d07 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -226,6 +226,7 @@ export interface IDockviewComponent extends IBaseGrid { readonly onDidMaximizedGroupChange: Event; readonly onDidPopoutGroupSizeChange: Event; readonly onDidPopoutGroupPositionChange: Event; + readonly onDidBlockPopout: Event; readonly options: DockviewComponentOptions; updateOptions(options: DockviewOptions): void; moveGroupOrPanel(options: MoveGroupOrPanelOptions): void; @@ -319,6 +320,9 @@ export class DockviewComponent readonly onDidPopoutGroupPositionChange: Event = this._onDidPopoutGroupPositionChange.event; + private readonly _onDidBlockPopout = new Emitter(); + readonly onDidBlockPopout: Event = this._onDidBlockPopout.event; + private readonly _onDidLayoutFromJSON = new Emitter(); readonly onDidLayoutFromJSON: Event = this._onDidLayoutFromJSON.event; @@ -505,6 +509,7 @@ export class DockviewComponent this._onDidOptionsChange, this._onDidPopoutGroupSizeChange, this._onDidPopoutGroupPositionChange, + this._onDidBlockPopout, this.onDidViewVisibilityChangeMicroTaskQueue(() => { this.updateWatermark(); }), @@ -715,6 +720,7 @@ export class DockviewComponent if (popoutContainer === null) { popoutWindowDisposable.dispose(); + this._onDidBlockPopout.fire(); return false; } @@ -734,7 +740,7 @@ export class DockviewComponent const referenceLocation = itemToPopout.api.location.type; /** - * The group that is being added doesn't already exist within the DOM, the most likely occurance + * The group that is being added doesn't already exist within the DOM, the most likely occurrence * of this case is when being called from the `fromJSON(...)` method */ const isGroupAddedToDom = From 477bf14767d30bc7a8b0c9016df7ef037dafc260 Mon Sep 17 00:00:00 2001 From: Mathias Borglin Date: Wed, 7 May 2025 13:20:16 +0200 Subject: [PATCH 2/6] Update sandbox with example usage --- .../docs/sandboxes/react/dockview/popout-group/src/app.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/docs/sandboxes/react/dockview/popout-group/src/app.tsx b/packages/docs/sandboxes/react/dockview/popout-group/src/app.tsx index fbca7560d..1af0cf9bc 100644 --- a/packages/docs/sandboxes/react/dockview/popout-group/src/app.tsx +++ b/packages/docs/sandboxes/react/dockview/popout-group/src/app.tsx @@ -143,6 +143,9 @@ export const App = (props: { theme?: string }) => { const load = (api: DockviewApi) => { api.clear(); + api.onDidBlockPopout(() => { + console.log('Popout blocked'); + }); if (layout) { try { api.fromJSON(layout); From 809b7665f515152f72ccfdee0226f9d81bc0cb57 Mon Sep 17 00:00:00 2001 From: Mathias Borglin Date: Fri, 9 May 2025 10:15:24 +0200 Subject: [PATCH 3/6] Return popouts whn blocked by browser --- .../src/dockview/components/titlebar/tabs.ts | 3 ++ .../src/dockview/dockviewComponent.ts | 46 ++++++++++++------- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabs.ts b/packages/dockview-core/src/dockview/components/titlebar/tabs.ts index 53486ed7c..062c1e683 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabs.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabs.ts @@ -251,6 +251,9 @@ export class Tabs extends CompositeDisposable { delete(id: string): void { const index = this.indexOf(id); const tabToRemove = this._tabs.splice(index, 1)[0]; + if (!tabToRemove) { + return; + } const { value, disposable } = tabToRemove; diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index ff1284d07..ee9035ab3 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -718,27 +718,11 @@ export class DockviewComponent return false; } - if (popoutContainer === null) { - popoutWindowDisposable.dispose(); - this._onDidBlockPopout.fire(); - return false; - } - - const gready = document.createElement('div'); - gready.className = 'dv-overlay-render-container'; - - const overlayRenderContainer = new OverlayRenderContainer( - gready, - this - ); - const referenceGroup = itemToPopout instanceof DockviewPanel ? itemToPopout.group : itemToPopout; - const referenceLocation = itemToPopout.api.location.type; - /** * The group that is being added doesn't already exist within the DOM, the most likely occurrence * of this case is when being called from the `fromJSON(...)` method @@ -757,6 +741,36 @@ export class DockviewComponent this._onDidAddGroup.fire(group); } + if (popoutContainer === null) { + popoutWindowDisposable.dispose(); + this._onDidBlockPopout.fire(); + + // if the popout window was blocked, we need to move the group back to the reference group + // and set it to visible + this.movingLock(() => + moveGroupWithoutDestroying({ + from: group, + to: referenceGroup, + }) + ); + + if (!referenceGroup.api.isVisible) { + referenceGroup.api.setVisible(true); + } + + return false; + } + + const gready = document.createElement('div'); + gready.className = 'dv-overlay-render-container'; + + const overlayRenderContainer = new OverlayRenderContainer( + gready, + this + ); + + const referenceLocation = itemToPopout.api.location.type; + group.model.renderContainer = overlayRenderContainer; group.layout( _window.window!.innerWidth, From 86e8e6371875665a6b8be13e2ee64c0651d0858a Mon Sep 17 00:00:00 2001 From: Mathias Borglin Date: Sun, 11 May 2025 21:42:39 +0200 Subject: [PATCH 4/6] Add tests --- .../dockview/dockviewComponent.spec.ts | 48 +++++++++++++++++++ .../src/dockview/dockviewComponent.ts | 4 +- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index 2c08f0708..02689e075 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -5791,6 +5791,54 @@ describe('dockviewComponent', () => { ]); }); + describe('when browsers block popups', () => { + let container: HTMLDivElement; + let dockview: DockviewComponent; + let panel: DockviewPanel; + + beforeEach(() => { + jest.spyOn(window, 'open').mockReturnValue(null); + + container = document.createElement('div'); + + dockview = new DockviewComponent(container, { + createComponent(options) { + switch (options.name) { + case 'default': + return new PanelContentPartTest( + options.id, + options.name + ); + default: + throw new Error(`unsupported`); + } + }, + }); + + dockview.layout(1000, 500); + + panel = dockview.addPanel({ + id: 'panel_1', + component: 'default', + }); + }); + + test('onDidBlockPopout event is emitted', async () => { + const onDidBlockPopoutHandler = jest.fn(); + dockview.onDidBlockPopout(onDidBlockPopoutHandler); + + await dockview.addPopoutGroup(panel.group); + + expect(onDidBlockPopoutHandler).toHaveBeenCalledTimes(1); + }); + + test('popout group is restored to its original position', async () => { + await dockview.addPopoutGroup(panel.group); + + expect(panel.group.api.location.type).toBe('grid'); + }); + }); + test('dispose of dockview instance when popup is open', async () => { const container = document.createElement('div'); diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index ee9035ab3..c06f1c776 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -738,7 +738,9 @@ export class DockviewComponent group = options.overridePopoutGroup; } else { group = this.createGroup({ id: groupId }); - this._onDidAddGroup.fire(group); + if (popoutContainer) { + this._onDidAddGroup.fire(group); + } } if (popoutContainer === null) { From 87c2fd517d60e8463d7ea68b9002b04d5a02140f Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Fri, 13 Jun 2025 22:56:29 +0100 Subject: [PATCH 5/6] chore: adjust public method names --- .../dockview/dockviewComponent.spec.ts | 4 ++-- .../dockview-core/src/api/component.api.ts | 4 ++-- .../src/dockview/components/titlebar/tabs.ts | 3 --- .../src/dockview/dockviewComponent.ts | 19 ++++++++++++------- .../react/dockview/popout-group/src/app.tsx | 2 +- 5 files changed, 17 insertions(+), 15 deletions(-) diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index 02689e075..8f72affee 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -5823,9 +5823,9 @@ describe('dockviewComponent', () => { }); }); - test('onDidBlockPopout event is emitted', async () => { + test('onDidOpenPoputWindowFail event is emitted', async () => { const onDidBlockPopoutHandler = jest.fn(); - dockview.onDidBlockPopout(onDidBlockPopoutHandler); + dockview.onDidOpenPopoutWindowFail(onDidBlockPopoutHandler); await dockview.addPopoutGroup(panel.group); diff --git a/packages/dockview-core/src/api/component.api.ts b/packages/dockview-core/src/api/component.api.ts index 555abf1f0..f919d779f 100644 --- a/packages/dockview-core/src/api/component.api.ts +++ b/packages/dockview-core/src/api/component.api.ts @@ -749,8 +749,8 @@ export class DockviewApi implements CommonApi { return this.component.onDidPopoutGroupPositionChange; } - get onDidBlockPopout(): Event { - return this.component.onDidBlockPopout; + get onDidOpenPopoutWindowFail(): Event { + return this.component.onDidOpenPopoutWindowFail; } /** diff --git a/packages/dockview-core/src/dockview/components/titlebar/tabs.ts b/packages/dockview-core/src/dockview/components/titlebar/tabs.ts index 062c1e683..53486ed7c 100644 --- a/packages/dockview-core/src/dockview/components/titlebar/tabs.ts +++ b/packages/dockview-core/src/dockview/components/titlebar/tabs.ts @@ -251,9 +251,6 @@ export class Tabs extends CompositeDisposable { delete(id: string): void { const index = this.indexOf(id); const tabToRemove = this._tabs.splice(index, 1)[0]; - if (!tabToRemove) { - return; - } const { value, disposable } = tabToRemove; diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index c06f1c776..2bed69abd 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -226,7 +226,7 @@ export interface IDockviewComponent extends IBaseGrid { readonly onDidMaximizedGroupChange: Event; readonly onDidPopoutGroupSizeChange: Event; readonly onDidPopoutGroupPositionChange: Event; - readonly onDidBlockPopout: Event; + readonly onDidOpenPopoutWindowFail: Event; readonly options: DockviewComponentOptions; updateOptions(options: DockviewOptions): void; moveGroupOrPanel(options: MoveGroupOrPanelOptions): void; @@ -320,8 +320,9 @@ export class DockviewComponent readonly onDidPopoutGroupPositionChange: Event = this._onDidPopoutGroupPositionChange.event; - private readonly _onDidBlockPopout = new Emitter(); - readonly onDidBlockPopout: Event = this._onDidBlockPopout.event; + private readonly _onDidOpenPopoutWindowFail = new Emitter(); + readonly onDidOpenPopoutWindowFail: Event = + this._onDidOpenPopoutWindowFail.event; private readonly _onDidLayoutFromJSON = new Emitter(); readonly onDidLayoutFromJSON: Event = this._onDidLayoutFromJSON.event; @@ -509,7 +510,7 @@ export class DockviewComponent this._onDidOptionsChange, this._onDidPopoutGroupSizeChange, this._onDidPopoutGroupPositionChange, - this._onDidBlockPopout, + this._onDidOpenPopoutWindowFail, this.onDidViewVisibilityChangeMicroTaskQueue(() => { this.updateWatermark(); }), @@ -739,13 +740,17 @@ export class DockviewComponent } else { group = this.createGroup({ id: groupId }); if (popoutContainer) { - this._onDidAddGroup.fire(group); + this._onDidAddGroup.fire(group); } } if (popoutContainer === null) { + console.error( + 'dockview: failed to create popout. perhaps you need to allow pop-ups for this website' + ); + popoutWindowDisposable.dispose(); - this._onDidBlockPopout.fire(); + this._onDidOpenPopoutWindowFail.fire(); // if the popout window was blocked, we need to move the group back to the reference group // and set it to visible @@ -988,7 +993,7 @@ export class DockviewComponent return true; }) .catch((err) => { - console.error('dockview: failed to create popout window', err); + console.error('dockview: failed to create popout.', err); return false; }); } diff --git a/packages/docs/sandboxes/react/dockview/popout-group/src/app.tsx b/packages/docs/sandboxes/react/dockview/popout-group/src/app.tsx index 1af0cf9bc..ce4585bac 100644 --- a/packages/docs/sandboxes/react/dockview/popout-group/src/app.tsx +++ b/packages/docs/sandboxes/react/dockview/popout-group/src/app.tsx @@ -143,7 +143,7 @@ export const App = (props: { theme?: string }) => { const load = (api: DockviewApi) => { api.clear(); - api.onDidBlockPopout(() => { + api.onDidOpenPopoutWindowFail(() => { console.log('Popout blocked'); }); if (layout) { From d7685db438f66e9e75e3aa8ebbce43a3d2bb49c6 Mon Sep 17 00:00:00 2001 From: mathuo <6710312+mathuo@users.noreply.github.com> Date: Mon, 16 Jun 2025 22:35:37 +0100 Subject: [PATCH 6/6] chore: fix --- .../src/dockview/dockviewComponent.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index d83ed7923..3ff0f2cca 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -720,10 +720,13 @@ export class DockviewComponent return false; } - const referenceGroup = - itemToPopout instanceof DockviewPanel - ? itemToPopout.group - : itemToPopout; + const referenceGroup = options?.referenceGroup + ? options.referenceGroup + : itemToPopout instanceof DockviewPanel + ? itemToPopout.group + : itemToPopout; + + const referenceLocation = itemToPopout.api.location.type; /** * The group that is being added doesn't already exist within the DOM, the most likely occurrence @@ -740,6 +743,7 @@ export class DockviewComponent group = options.overridePopoutGroup; } else { group = this.createGroup({ id: groupId }); + if (popoutContainer) { this._onDidAddGroup.fire(group); } @@ -777,8 +781,6 @@ export class DockviewComponent this ); - const referenceLocation = itemToPopout.api.location.type; - group.model.renderContainer = overlayRenderContainer; group.layout( _window.window!.innerWidth,