Skip to content

Commit 256f93b

Browse files
committed
fix(cdk/stepper): focus management not working with shadow dom encapsulation
The CDK stepper focus management checks against the document to find the focused element which won't work if the stepper is inside the shadow DOM. These changes use our shadow DOM helper to resolve the element instead.
1 parent 3531346 commit 256f93b

File tree

4 files changed

+41
-3
lines changed

4 files changed

+41
-3
lines changed

src/cdk/stepper/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ ng_module(
1515
"//src/cdk/bidi",
1616
"//src/cdk/coercion",
1717
"//src/cdk/keycodes",
18+
"//src/cdk/platform",
1819
"@npm//@angular/core",
1920
"@npm//@angular/forms",
2021
"@npm//rxjs",

src/cdk/stepper/stepper.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
ViewEncapsulation,
4141
AfterContentInit,
4242
} from '@angular/core';
43+
import {_getFocusedElementPierceShadowDom} from '@angular/cdk/platform';
4344
import {Observable, of as observableOf, Subject} from 'rxjs';
4445
import {startWith, takeUntil} from 'rxjs/operators';
4546

@@ -534,7 +535,7 @@ export class CdkStepper implements AfterContentInit, AfterViewInit, OnDestroy {
534535
/** Checks whether the stepper contains the focused element. */
535536
private _containsFocus(): boolean {
536537
const stepperElement = this._elementRef.nativeElement;
537-
const focusedElement = this._document.activeElement;
538+
const focusedElement = _getFocusedElementPierceShadowDom();
538539
return stepperElement === focusedElement || stepperElement.contains(focusedElement);
539540
}
540541

src/material/stepper/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ ng_test_library(
7676
":stepper",
7777
"//src/cdk/bidi",
7878
"//src/cdk/keycodes",
79+
"//src/cdk/platform",
7980
"//src/cdk/stepper",
8081
"//src/cdk/testing/private",
8182
"//src/material/core",

src/material/stepper/stepper.spec.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
ViewChildren,
3131
QueryList,
3232
ViewChild,
33+
ViewEncapsulation,
3334
} from '@angular/core';
3435
import {ComponentFixture, fakeAsync, flush, inject, TestBed} from '@angular/core/testing';
3536
import {
@@ -45,6 +46,7 @@ import {
4546
import {MatRipple, ThemePalette} from '@angular/material/core';
4647
import {By} from '@angular/platform-browser';
4748
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
49+
import {_supportsShadowDom} from '@angular/cdk/platform';
4850
import {merge, Observable, Subject} from 'rxjs';
4951
import {map, take} from 'rxjs/operators';
5052
import {MatStepHeader, MatStepperModule} from './index';
@@ -287,6 +289,31 @@ describe('MatStepper', () => {
287289
expect(stepHeaderEl.focus).toHaveBeenCalled();
288290
});
289291

292+
it('should focus next step header if focus is inside the stepper with shadow DOM', () => {
293+
if (!_supportsShadowDom()) {
294+
return;
295+
}
296+
297+
fixture.destroy();
298+
TestBed.resetTestingModule();
299+
fixture = createComponent(SimpleMatVerticalStepperApp, [], [], ViewEncapsulation.ShadowDom);
300+
fixture.detectChanges();
301+
302+
const stepperComponent =
303+
fixture.debugElement.query(By.directive(MatStepper))!.componentInstance;
304+
const stepHeaderEl =
305+
fixture.debugElement.queryAll(By.css('mat-step-header'))[1].nativeElement;
306+
const nextButtonNativeEl = fixture.debugElement
307+
.queryAll(By.directive(MatStepperNext))[0].nativeElement;
308+
spyOn(stepHeaderEl, 'focus');
309+
nextButtonNativeEl.focus();
310+
nextButtonNativeEl.click();
311+
fixture.detectChanges();
312+
313+
expect(stepperComponent.selectedIndex).toBe(1);
314+
expect(stepHeaderEl.focus).toHaveBeenCalled();
315+
});
316+
290317
it('should only be able to return to a previous step if it is editable', () => {
291318
const stepperComponent =
292319
fixture.debugElement.query(By.directive(MatStepper))!.componentInstance;
@@ -1559,7 +1586,8 @@ function asyncValidator(minLength: number, validationTrigger: Subject<void>): As
15591586

15601587
function createComponent<T>(component: Type<T>,
15611588
providers: Provider[] = [],
1562-
imports: any[] = []): ComponentFixture<T> {
1589+
imports: any[] = [],
1590+
encapsulation?: ViewEncapsulation): ComponentFixture<T> {
15631591
TestBed.configureTestingModule({
15641592
imports: [
15651593
MatStepperModule,
@@ -1572,8 +1600,15 @@ function createComponent<T>(component: Type<T>,
15721600
{provide: Directionality, useFactory: () => dir},
15731601
...providers
15741602
],
1575-
}).compileComponents();
1603+
});
1604+
1605+
if (encapsulation != null) {
1606+
TestBed.overrideComponent(component, {
1607+
set: {encapsulation}
1608+
});
1609+
}
15761610

1611+
TestBed.compileComponents();
15771612
return TestBed.createComponent<T>(component);
15781613
}
15791614

0 commit comments

Comments
 (0)