Skip to content

Commit e731203

Browse files
authored
fix(material/dialog): Make autofocus work with animations disabled (angular#29195)
1 parent 566057b commit e731203

File tree

3 files changed

+40
-18
lines changed

3 files changed

+40
-18
lines changed

src/cdk/dialog/dialog-container.ts

+33-6
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,13 @@ import {
3131
ElementRef,
3232
EmbeddedViewRef,
3333
Inject,
34+
Injector,
3435
NgZone,
3536
OnDestroy,
3637
Optional,
3738
ViewChild,
3839
ViewEncapsulation,
40+
afterNextRender,
3941
inject,
4042
} from '@angular/core';
4143
import {DialogConfig} from './dialog-config';
@@ -102,6 +104,10 @@ export class CdkDialogContainer<C extends DialogConfig = DialogConfig>
102104

103105
protected readonly _changeDetectorRef = inject(ChangeDetectorRef);
104106

107+
private _injector = inject(Injector);
108+
109+
private _isDestroyed = false;
110+
105111
constructor(
106112
protected _elementRef: ElementRef,
107113
protected _focusTrapFactory: FocusTrapFactory,
@@ -150,6 +156,7 @@ export class CdkDialogContainer<C extends DialogConfig = DialogConfig>
150156
}
151157

152158
ngOnDestroy() {
159+
this._isDestroyed = true;
153160
this._restoreFocus();
154161
}
155162

@@ -246,23 +253,33 @@ export class CdkDialogContainer<C extends DialogConfig = DialogConfig>
246253
* cannot be moved then focus will go to the dialog container.
247254
*/
248255
protected _trapFocus() {
256+
if (this._isDestroyed) {
257+
return;
258+
}
259+
249260
const element = this._elementRef.nativeElement;
250261
// If were to attempt to focus immediately, then the content of the dialog would not yet be
251262
// ready in instances where change detection has to run first. To deal with this, we simply
252263
// wait for the microtask queue to be empty when setting focus when autoFocus isn't set to
253264
// dialog. If the element inside the dialog can't be focused, then the container is focused
254265
// so the user can't tab into other elements behind it.
255-
switch (this._config.autoFocus) {
266+
const autoFocus = this._config.autoFocus;
267+
switch (autoFocus) {
256268
case false:
257269
case 'dialog':
258270
// Ensure that focus is on the dialog container. It's possible that a different
259271
// component tried to move focus while the open animation was running. See:
260272
// https://github.com/angular/components/issues/16215. Note that we only want to do this
261273
// if the focus isn't inside the dialog already, because it's possible that the consumer
262274
// turned off `autoFocus` in order to move focus themselves.
263-
if (!this._containsFocus()) {
264-
element.focus();
265-
}
275+
afterNextRender(
276+
() => {
277+
if (!this._containsFocus()) {
278+
element.focus();
279+
}
280+
},
281+
{injector: this._injector},
282+
);
266283
break;
267284
case true:
268285
case 'first-tabbable':
@@ -275,10 +292,20 @@ export class CdkDialogContainer<C extends DialogConfig = DialogConfig>
275292
});
276293
break;
277294
case 'first-heading':
278-
this._focusByCssSelector('h1, h2, h3, h4, h5, h6, [role="heading"]');
295+
afterNextRender(
296+
() => {
297+
this._focusByCssSelector('h1, h2, h3, h4, h5, h6, [role="heading"]');
298+
},
299+
{injector: this._injector},
300+
);
279301
break;
280302
default:
281-
this._focusByCssSelector(this._config.autoFocus!);
303+
afterNextRender(
304+
() => {
305+
this._focusByCssSelector(autoFocus!);
306+
},
307+
{injector: this._injector},
308+
);
282309
break;
283310
}
284311
}

src/material/dialog/dialog-container.ts

-8
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,6 @@ export class MatDialogContainer extends CdkDialogContainer<MatDialogConfig> impl
9494
/** Current timer for dialog animations. */
9595
private _animationTimer: ReturnType<typeof setTimeout> | null = null;
9696

97-
private _isDestroyed = false;
98-
9997
constructor(
10098
elementRef: ElementRef,
10199
focusTrapFactory: FocusTrapFactory,
@@ -264,10 +262,6 @@ export class MatDialogContainer extends CdkDialogContainer<MatDialogConfig> impl
264262
* be called by sub-classes that use different animation implementations.
265263
*/
266264
protected _openAnimationDone(totalTime: number) {
267-
if (this._isDestroyed) {
268-
return;
269-
}
270-
271265
if (this._config.delayFocusTrap) {
272266
this._trapFocus();
273267
}
@@ -281,8 +275,6 @@ export class MatDialogContainer extends CdkDialogContainer<MatDialogConfig> impl
281275
if (this._animationTimer !== null) {
282276
clearTimeout(this._animationTimer);
283277
}
284-
285-
this._isDestroyed = true;
286278
}
287279

288280
override attachComponentPortal<T>(portal: ComponentPortal<T>): ComponentRef<T> {

src/material/dialog/dialog.spec.ts

+7-4
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ import {
3030
ViewEncapsulation,
3131
createNgModuleRef,
3232
forwardRef,
33-
signal,
3433
provideZoneChangeDetection,
34+
signal,
3535
} from '@angular/core';
3636
import {
3737
ComponentFixture,
@@ -1041,7 +1041,8 @@ describe('MDC-based MatDialog', () => {
10411041
});
10421042

10431043
viewContainerFixture.detectChanges();
1044-
flushMicrotasks();
1044+
flush();
1045+
viewContainerFixture.detectChanges();
10451046

10461047
let backdrop = overlayContainerElement.querySelector(
10471048
'.cdk-overlay-backdrop',
@@ -1209,7 +1210,8 @@ describe('MDC-based MatDialog', () => {
12091210
});
12101211

12111212
viewContainerFixture.detectChanges();
1212-
flushMicrotasks();
1213+
flush();
1214+
viewContainerFixture.detectChanges();
12131215

12141216
let container = overlayContainerElement.querySelector(
12151217
'.mat-mdc-dialog-container',
@@ -1245,7 +1247,8 @@ describe('MDC-based MatDialog', () => {
12451247
});
12461248

12471249
viewContainerFixture.detectChanges();
1248-
flushMicrotasks();
1250+
flush();
1251+
viewContainerFixture.detectChanges();
12491252

12501253
let firstParagraph = overlayContainerElement.querySelector(
12511254
'p[tabindex="-1"]',

0 commit comments

Comments
 (0)