Skip to content

Commit de22048

Browse files
authored
fix(cdk-experimental/dialog): focus restoration not working inside shadow dom (#23194)
Fixes that the experimental CDK dialog doesn't pick up the correct element to restore focus to from the shadow DOM.
1 parent 78ba3b1 commit de22048

File tree

3 files changed

+43
-5
lines changed

3 files changed

+43
-5
lines changed

src/cdk-experimental/dialog/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ ng_module(
1515
"//src/cdk/bidi",
1616
"//src/cdk/keycodes",
1717
"//src/cdk/overlay",
18+
"//src/cdk/platform",
1819
"//src/cdk/portal",
1920
"@npm//@angular/animations",
2021
"@npm//@angular/core",
@@ -38,6 +39,7 @@ ng_test_library(
3839
"//src/cdk/bidi",
3940
"//src/cdk/keycodes",
4041
"//src/cdk/overlay",
42+
"//src/cdk/platform",
4143
"//src/cdk/testing/private",
4244
"@npm//@angular/common",
4345
"@npm//@angular/platform-browser",

src/cdk-experimental/dialog/dialog-container.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import {animate, AnimationEvent, state, style, transition, trigger} from '@angular/animations';
1010
import {FocusTrapFactory} from '@angular/cdk/a11y';
11+
import {_getFocusedElementPierceShadowDom} from '@angular/cdk/platform';
1112
import {
1213
BasePortalOutlet,
1314
CdkPortalOutlet,
@@ -228,7 +229,7 @@ export class CdkDialogContainer extends BasePortalOutlet implements OnDestroy {
228229
/** Saves a reference to the element that was focused before the dialog was opened. */
229230
private _savePreviouslyFocusedElement() {
230231
if (this._document) {
231-
this._elementFocusedBeforeDialogWasOpened = this._document.activeElement as HTMLElement;
232+
this._elementFocusedBeforeDialogWasOpened = _getFocusedElementPierceShadowDom();
232233
}
233234
}
234235

@@ -259,7 +260,7 @@ export class CdkDialogContainer extends BasePortalOutlet implements OnDestroy {
259260
}
260261
});
261262
} else {
262-
const activeElement = this._document.activeElement;
263+
const activeElement = _getFocusedElementPierceShadowDom();
263264

264265
// Otherwise ensure that focus is on the dialog container. It's possible that a different
265266
// component tried to move focus while the open animation was running. See:

src/cdk-experimental/dialog/dialog.spec.ts

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import {
1616
NgModule,
1717
TemplateRef,
1818
ViewChild,
19-
ViewContainerRef
19+
ViewContainerRef,
20+
ViewEncapsulation
2021
} from '@angular/core';
2122
import {By} from '@angular/platform-browser';
2223
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
@@ -26,11 +27,12 @@ import {Directionality} from '@angular/cdk/bidi';
2627
import {CdkDialogContainer} from './dialog-container';
2728
import {OverlayContainer} from '@angular/cdk/overlay';
2829
import {A, ESCAPE} from '@angular/cdk/keycodes';
30+
import {_supportsShadowDom} from '@angular/cdk/platform';
2931
import {
3032
dispatchKeyboardEvent,
3133
createKeyboardEvent,
3234
dispatchEvent,
33-
} from '../../cdk/testing/private';
35+
} from '@angular/cdk/testing/private';
3436
import {DIALOG_DATA, Dialog, DialogModule, DialogRef} from './index';
3537

3638
describe('Dialog', () => {
@@ -935,6 +937,32 @@ describe('Dialog', () => {
935937
document.body.removeChild(button);
936938
}));
937939

940+
it('should re-focus trigger element inside the shadow DOM when dialog closes', fakeAsync(() => {
941+
if (!_supportsShadowDom()) {
942+
return;
943+
}
944+
945+
viewContainerFixture.destroy();
946+
const fixture = TestBed.createComponent(ShadowDomComponent);
947+
fixture.detectChanges();
948+
const button = fixture.debugElement.query(By.css('button'))!.nativeElement;
949+
950+
button.focus();
951+
952+
const dialogRef = dialog.openFromComponent(PizzaMsg);
953+
flushMicrotasks();
954+
fixture.detectChanges();
955+
flushMicrotasks();
956+
957+
const spy = spyOn(button, 'focus').and.callThrough();
958+
dialogRef.close();
959+
flushMicrotasks();
960+
fixture.detectChanges();
961+
tick(500);
962+
963+
expect(spy).toHaveBeenCalled();
964+
}));
965+
938966
it('should not move focus if it was moved outside the dialog while animating', fakeAsync(() => {
939967
// Create a element that has focus before the dialog is opened.
940968
const button = document.createElement('button');
@@ -1220,6 +1248,12 @@ class DialogWithInjectedData {
12201248
@Component({template: '<p>Pasta</p>'})
12211249
class DialogWithoutFocusableElements {}
12221250

1251+
@Component({
1252+
template: `<button>I'm a button</button>`,
1253+
encapsulation: ViewEncapsulation.ShadowDom
1254+
})
1255+
class ShadowDomComponent {}
1256+
12231257
// Create a real (non-test) NgModule as a workaround for
12241258
// https://github.com/angular/angular/issues/10760
12251259
const TEST_DIRECTIVES = [
@@ -1230,7 +1264,8 @@ const TEST_DIRECTIVES = [
12301264
ComponentWithOnPushViewContainer,
12311265
ContentElementDialog,
12321266
DialogWithInjectedData,
1233-
DialogWithoutFocusableElements
1267+
DialogWithoutFocusableElements,
1268+
ShadowDomComponent,
12341269
];
12351270

12361271
@NgModule({

0 commit comments

Comments
 (0)