From 570ccdd3b0b7ecc8ade6b185ce793ac52fa93584 Mon Sep 17 00:00:00 2001 From: Bryan Cunningham Date: Thu, 18 Dec 2025 11:09:02 -0500 Subject: [PATCH] add some logic to refocus re-rendered/dicaonnected items --- libs/components/src/dialog/dialog.service.ts | 35 +++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/libs/components/src/dialog/dialog.service.ts b/libs/components/src/dialog/dialog.service.ts index 7753ae15774..5e4740e2444 100644 --- a/libs/components/src/dialog/dialog.service.ts +++ b/libs/components/src/dialog/dialog.service.ts @@ -160,16 +160,15 @@ export class CdkDialogRef implements DialogRef { /** This is not available until after construction, @see DialogService.open. */ cdkDialogRefBase!: CdkDialogRefBase; + private _closed = new Subject(); + readonly closed = this._closed.asObservable(); + // --- Delegated to CdkDialogRefBase --- close(result?: R, options?: DialogCloseOptions): void { this.cdkDialogRefBase.close(result, options); } - get closed(): Observable { - return this.cdkDialogRefBase.closed; - } - get disableClose(): boolean | undefined { return this.cdkDialogRefBase.disableClose; } @@ -181,6 +180,16 @@ export class CdkDialogRef implements DialogRef { get componentInstance(): C | null { return this.cdkDialogRefBase.componentInstance; } + + /** Called by DialogService.open() after the base dialog is created */ + _connectToCdkDialogRef(base: CdkDialogRefBase) { + this.cdkDialogRefBase = base; + // Forward closed events from the base dialog to our subject + base.closed.subscribe({ + next: (value) => this._closed.next(value), + complete: () => this._closed.complete(), + }); + } } @Injectable() @@ -227,25 +236,33 @@ export class DialogService { * This allows us to create the class instance and provide the base instance later, almost like "deferred inheritance". **/ const ref = new CdkDialogRef(); + const elementToRestore = + document.activeElement !== document.body ? (document.activeElement as HTMLElement) : null; + const injector = this.createInjector({ data: config?.data, dialogRef: ref, }); - // Merge the custom config with the default config const _config = { backdropClass: this.backDropClasses, scrollStrategy: this.defaultScrollStrategy, positionStrategy: config?.positionStrategy ?? new ResponsivePositionStrategy(), - restoreFocus: true, injector, ...config, }; - // Delay dialog opening to allow menus to close and restore focus to their triggers. - // This ensures proper focus restoration when dialogs opened from menus are closed. setTimeout(() => { - ref.cdkDialogRefBase = this.dialog.open(componentOrTemplateRef, _config); + const baseDialogRef = this.dialog.open(componentOrTemplateRef, _config); + + // Restore focus when dialog closes unless consumer specified custom behavior + if (elementToRestore && !config?.restoreFocus) { + baseDialogRef.closed.subscribe(() => { + setTimeout(() => elementToRestore.focus(), 0); + }); + } + + ref._connectToCdkDialogRef(baseDialogRef); }, 0); return ref;