diff --git a/packages/dockview-core/src/__tests__/events.spec.ts b/packages/dockview-core/src/__tests__/events.spec.ts index 301feb3623..35c46774c2 100644 --- a/packages/dockview-core/src/__tests__/events.spec.ts +++ b/packages/dockview-core/src/__tests__/events.spec.ts @@ -44,24 +44,6 @@ describe('events', () => { expect(value).toBeUndefined(); }); - it('should stop emitting after dispose', () => { - const emitter = new Emitter(); - let value: number | undefined = undefined; - - const stream = emitter.event((x) => { - value = x; - }); - - emitter.fire(0); - expect(value).toBe(0); - - stream.dispose(); - - value = undefined; - emitter.fire(1); - expect(value).toBeUndefined(); - }); - it('should replay last value in replay mode', () => { const emitter = new Emitter({ replay: true }); let value: number | undefined = undefined; @@ -76,7 +58,7 @@ describe('events', () => { stream.dispose(); }); - it('should not replay last value in replay mode', () => { + it('should not replay last value when not in replay mode', () => { const emitter = new Emitter(); let value: number | undefined = undefined; @@ -284,4 +266,89 @@ describe('events', () => { undefined ); }); + + describe('pausing and resuming events', () => { + it('should not fire events when paused', () => { + const emitter = new Emitter(); + let value: number | undefined = undefined; + + const stream = emitter.event((x) => { + value = x; + }); + + const pauseDisposable = emitter.pauseEvents(); + + emitter.fire(0); + expect(value).toBeUndefined(); + + emitter.fire(1); + expect(value).toBeUndefined(); + + pauseDisposable.dispose(); + stream.dispose(); + }); + + it('should fire events fired after resuming', () => { + const emitter = new Emitter(); + let value: number | undefined = undefined; + + const stream = emitter.event((x) => { + value = x; + }); + + const pauseDisposable = emitter.pauseEvents(); + + emitter.fire(0); + expect(value).toBeUndefined(); + + pauseDisposable.dispose(); + + emitter.fire(1); + expect(value).toBe(1); + + stream.dispose(); + }); + + it('should not replay values fired while paused when in replay mode', () => { + const emitter = new Emitter({ replay: true }); + let value: number | undefined = undefined; + + const pauseDisposable = emitter.pauseEvents(); + + emitter.fire(1); + + const stream = emitter.event((x) => { + value = x; + }); + expect(value).toBeUndefined(); + + pauseDisposable.dispose(); + stream.dispose(); + }); + + it('should allow multiple pause tokens to each pause event emissions', () => { + const emitter = new Emitter(); + let value: number | undefined = undefined; + + const stream = emitter.event((x) => { + value = x; + }); + + const pauseDisposable1 = emitter.pauseEvents(); + const pauseDisposable2 = emitter.pauseEvents(); + + emitter.fire(0); + expect(value).toBeUndefined(); + + pauseDisposable1.dispose(); + emitter.fire(1); + expect(value).toBeUndefined(); + + pauseDisposable2.dispose(); + emitter.fire(2); + expect(value).toBe(2); + + stream.dispose(); + }); + }); }); diff --git a/packages/dockview-core/src/__tests__/gridview/gridview.spec.ts b/packages/dockview-core/src/__tests__/gridview/gridview.spec.ts index 2206fe3e15..9066c49d52 100644 --- a/packages/dockview-core/src/__tests__/gridview/gridview.spec.ts +++ b/packages/dockview-core/src/__tests__/gridview/gridview.spec.ts @@ -1316,4 +1316,24 @@ describe('gridview', () => { expect(() => gridview.normalize()).not.toThrow(); }); }); + + test('that serialize does not cause an onDidMaximizedNodeChange event', () => { + const gridview = new Gridview( + true, + { separatorBorder: '' }, + Orientation.HORIZONTAL + ); + gridview.layout(1000, 1000); + + let counter = 0; + const subscription = gridview.onDidMaximizedNodeChange(() => { + counter++; + }); + + gridview.serialize(); + + expect(counter).toBe(0); + + subscription.dispose(); + }); }); diff --git a/packages/dockview-core/src/events.ts b/packages/dockview-core/src/events.ts index 30cde97b38..b84a683af9 100644 --- a/packages/dockview-core/src/events.ts +++ b/packages/dockview-core/src/events.ts @@ -1,4 +1,4 @@ -import { IDisposable } from './lifecycle'; +import { Disposable, IDisposable } from './lifecycle'; export interface Event { (listener: (e: T) => any): IDisposable; @@ -105,6 +105,8 @@ export class Emitter implements IDisposable { private _listeners: Listener[] = []; private _disposed = false; + private readonly _pauseTokens = new Set(); + static ENABLE_TRACKING = false; static readonly MEMORY_LEAK_WATCHER = new LeakageMonitor(); @@ -160,6 +162,9 @@ export class Emitter implements IDisposable { } public fire(e: T): void { + if (this._pauseTokens.size > 0) { + return; + } if (this.options?.replay) { this._last = e; } @@ -168,6 +173,12 @@ export class Emitter implements IDisposable { } } + public pauseEvents(): IDisposable { + const lock = {}; + this._pauseTokens.add(lock); + return Disposable.from(() => this._pauseTokens.delete(lock)); + } + public dispose(): void { if (!this._disposed) { this._disposed = true; diff --git a/packages/dockview-core/src/gridview/gridview.ts b/packages/dockview-core/src/gridview/gridview.ts index 73590cda61..782aed4da4 100644 --- a/packages/dockview-core/src/gridview/gridview.ts +++ b/packages/dockview-core/src/gridview/gridview.ts @@ -511,39 +511,50 @@ export class Gridview implements IDisposable { maxmizedViewLocation = getGridLocation(maximizedView.element); } - if (this.hasMaximizedView()) { - /** - * the saved layout cannot be in its maxmized state otherwise all of the underlying - * view dimensions will be wrong - * - * To counteract this we temporaily remove the maximized view to compute the serialized output - * of the grid before adding back the maxmized view as to not alter the layout from the users - * perspective when `.toJSON()` is called - */ - this.exitMaximizedView(); - } + /** + * We pause the onDidMaximizedNodeChange events because this method needs to + * call `this.exitMaximizedView()`. We don't want this to invoke any listeners + * since we undo it before leaving this method + */ + const pauseToken = this._onDidMaximizedNodeChange.pauseEvents(); - const root = serializeBranchNode(this.getView(), this.orientation); + try { + if (this.hasMaximizedView()) { + /** + * the saved layout cannot be in its maxmized state otherwise all of the underlying + * view dimensions will be wrong + * + * To counteract this we temporaily remove the maximized view to compute the serialized output + * of the grid before adding back the maxmized view as to not alter the layout from the users + * perspective when `.toJSON()` is called + */ + this.exitMaximizedView(); + } - const resullt: SerializedGridview = { - root, - width: this.width, - height: this.height, - orientation: this.orientation, - }; - - if (maxmizedViewLocation) { - resullt.maximizedNode = { - location: maxmizedViewLocation, + const root = serializeBranchNode(this.getView(), this.orientation); + + const result: SerializedGridview = { + root, + width: this.width, + height: this.height, + orientation: this.orientation, }; - } - if (maximizedView) { - // replace any maximzied view that was removed for serialization purposes - this.maximizeView(maximizedView); - } + if (maxmizedViewLocation) { + result.maximizedNode = { + location: maxmizedViewLocation, + }; + } + + if (maximizedView) { + // replace any maximzied view that was removed for serialization purposes + this.maximizeView(maximizedView); + } - return resullt; + return result; + } finally { + pauseToken.dispose(); + } } public dispose(): void {