Skip to content

Commit c0a224b

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 c0a224b

File tree

4 files changed

+42
-4
lines changed

4 files changed

+42
-4
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: 38 additions & 3 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';
@@ -271,7 +273,32 @@ describe('MatStepper', () => {
271273
expect(stepHeaderEl.focus).not.toHaveBeenCalled();
272274
});
273275

274-
it('should focus next step header if focus is inside the stepper', () => {
276+
fit('should focus next step header if focus is inside the stepper', () => {
277+
const stepperComponent =
278+
fixture.debugElement.query(By.directive(MatStepper))!.componentInstance;
279+
const stepHeaderEl =
280+
fixture.debugElement.queryAll(By.css('mat-step-header'))[1].nativeElement;
281+
const nextButtonNativeEl = fixture.debugElement
282+
.queryAll(By.directive(MatStepperNext))[0].nativeElement;
283+
spyOn(stepHeaderEl, 'focus');
284+
nextButtonNativeEl.focus();
285+
nextButtonNativeEl.click();
286+
fixture.detectChanges();
287+
288+
expect(stepperComponent.selectedIndex).toBe(1);
289+
expect(stepHeaderEl.focus).toHaveBeenCalled();
290+
});
291+
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+
275302
const stepperComponent =
276303
fixture.debugElement.query(By.directive(MatStepper))!.componentInstance;
277304
const stepHeaderEl =
@@ -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)