diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index c8619b8e1..1ef2c8f4c 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -5852,6 +5852,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('onDidOpenPoputWindowFail event is emitted', async () => { + const onDidBlockPopoutHandler = jest.fn(); + dockview.onDidOpenPopoutWindowFail(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/api/component.api.ts b/packages/dockview-core/src/api/component.api.ts index 4138ff6f2..f919d779f 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 onDidOpenPopoutWindowFail(): Event { + return this.component.onDidOpenPopoutWindowFail; + } + /** * All panel objects. */ diff --git a/packages/dockview-core/src/dockview/dockviewComponent.ts b/packages/dockview-core/src/dockview/dockviewComponent.ts index 78a673ff4..3ff0f2cca 100644 --- a/packages/dockview-core/src/dockview/dockviewComponent.ts +++ b/packages/dockview-core/src/dockview/dockviewComponent.ts @@ -227,6 +227,7 @@ export interface IDockviewComponent extends IBaseGrid { readonly onDidMaximizedGroupChange: Event; readonly onDidPopoutGroupSizeChange: Event; readonly onDidPopoutGroupPositionChange: Event; + readonly onDidOpenPopoutWindowFail: Event; readonly options: DockviewComponentOptions; updateOptions(options: DockviewOptions): void; moveGroupOrPanel(options: MoveGroupOrPanelOptions): void; @@ -320,6 +321,10 @@ export class DockviewComponent readonly onDidPopoutGroupPositionChange: Event = this._onDidPopoutGroupPositionChange.event; + private readonly _onDidOpenPopoutWindowFail = new Emitter(); + readonly onDidOpenPopoutWindowFail: Event = + this._onDidOpenPopoutWindowFail.event; + private readonly _onDidLayoutFromJSON = new Emitter(); readonly onDidLayoutFromJSON: Event = this._onDidLayoutFromJSON.event; @@ -506,6 +511,7 @@ export class DockviewComponent this._onDidOptionsChange, this._onDidPopoutGroupSizeChange, this._onDidPopoutGroupPositionChange, + this._onDidOpenPopoutWindowFail, this.onDidViewVisibilityChangeMicroTaskQueue(() => { this.updateWatermark(); }), @@ -714,19 +720,6 @@ export class DockviewComponent return false; } - if (popoutContainer === null) { - popoutWindowDisposable.dispose(); - return false; - } - - const gready = document.createElement('div'); - gready.className = 'dv-overlay-render-container'; - - const overlayRenderContainer = new OverlayRenderContainer( - gready, - this - ); - const referenceGroup = options?.referenceGroup ? options.referenceGroup : itemToPopout instanceof DockviewPanel @@ -736,7 +729,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 = @@ -750,9 +743,44 @@ 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) { + console.error( + 'dockview: failed to create popout. perhaps you need to allow pop-ups for this website' + ); + + popoutWindowDisposable.dispose(); + 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 + 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 + ); + group.model.renderContainer = overlayRenderContainer; group.layout( _window.window!.innerWidth, @@ -968,7 +996,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 fbca7560d..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,6 +143,9 @@ export const App = (props: { theme?: string }) => { const load = (api: DockviewApi) => { api.clear(); + api.onDidOpenPopoutWindowFail(() => { + console.log('Popout blocked'); + }); if (layout) { try { api.fromJSON(layout);