Skip to content

Commit c23de0e

Browse files
crisbetojelbourn
authored andcommitted
fix(a11y): focus trap directive not capturing focus if auto capture input is set after init (#19689)
Currently the `cdkTrapFocusAutoCapture` is only checked once on init. These changes make it so that it's respected after initialization as well. Fixes ##19664.
1 parent da56af1 commit c23de0e

File tree

3 files changed

+45
-5
lines changed

3 files changed

+45
-5
lines changed

src/cdk/a11y/focus-trap/focus-trap.spec.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,28 @@ describe('FocusTrap', () => {
186186
expect(document.activeElement).toBe(buttonOutsideTrappedRegion);
187187
});
188188
}));
189+
190+
it('should capture focus if auto capture is enabled later on', async(() => {
191+
const fixture = TestBed.createComponent(FocusTrapWithAutoCapture);
192+
fixture.componentInstance.autoCaptureEnabled = false;
193+
fixture.componentInstance.showTrappedRegion = true;
194+
fixture.detectChanges();
195+
196+
const buttonOutsideTrappedRegion = fixture.nativeElement.querySelector('button');
197+
buttonOutsideTrappedRegion.focus();
198+
expect(document.activeElement).toBe(buttonOutsideTrappedRegion);
199+
200+
fixture.componentInstance.autoCaptureEnabled = true;
201+
fixture.detectChanges();
202+
203+
fixture.whenStable().then(() => {
204+
expect(document.activeElement!.id).toBe('auto-capture-target');
205+
206+
fixture.destroy();
207+
expect(document.activeElement).toBe(buttonOutsideTrappedRegion);
208+
});
209+
}));
210+
189211
});
190212
});
191213

@@ -205,7 +227,7 @@ class SimpleFocusTrap {
205227
@Component({
206228
template: `
207229
<button type="button">Toggle</button>
208-
<div *ngIf="showTrappedRegion" cdkTrapFocus cdkTrapFocusAutoCapture>
230+
<div *ngIf="showTrappedRegion" cdkTrapFocus [cdkTrapFocusAutoCapture]="autoCaptureEnabled">
209231
<input id="auto-capture-target">
210232
<button>SAVE</button>
211233
</div>
@@ -214,6 +236,7 @@ class SimpleFocusTrap {
214236
class FocusTrapWithAutoCapture {
215237
@ViewChild(CdkTrapFocus) focusTrapDirective: CdkTrapFocus;
216238
showTrappedRegion = false;
239+
autoCaptureEnabled = true;
217240
}
218241

219242

src/cdk/a11y/focus-trap/focus-trap.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {
1919
OnDestroy,
2020
DoCheck,
2121
isDevMode,
22+
SimpleChanges,
23+
OnChanges,
2224
} from '@angular/core';
2325
import {take} from 'rxjs/operators';
2426
import {InteractivityChecker} from '../interactivity-checker/interactivity-checker';
@@ -89,6 +91,7 @@ export class FocusTrap {
8991
}
9092

9193
this._startAnchor = this._endAnchor = null;
94+
this._hasAttached = false;
9295
}
9396

9497
/**
@@ -378,7 +381,7 @@ export class FocusTrapFactory {
378381
selector: '[cdkTrapFocus]',
379382
exportAs: 'cdkTrapFocus',
380383
})
381-
export class CdkTrapFocus implements OnDestroy, AfterContentInit, DoCheck {
384+
export class CdkTrapFocus implements OnDestroy, AfterContentInit, OnChanges, DoCheck {
382385
private _document: Document;
383386

384387
/** Underlying FocusTrap instance. */
@@ -425,8 +428,7 @@ export class CdkTrapFocus implements OnDestroy, AfterContentInit, DoCheck {
425428
this.focusTrap.attachAnchors();
426429

427430
if (this.autoCapture) {
428-
this._previouslyFocusedElement = this._document.activeElement as HTMLElement;
429-
this.focusTrap.focusInitialElementWhenReady();
431+
this._captureFocus();
430432
}
431433
}
432434

@@ -436,6 +438,20 @@ export class CdkTrapFocus implements OnDestroy, AfterContentInit, DoCheck {
436438
}
437439
}
438440

441+
ngOnChanges(changes: SimpleChanges) {
442+
const autoCaptureChange = changes['autoCapture'];
443+
444+
if (autoCaptureChange && !autoCaptureChange.firstChange && this.autoCapture &&
445+
this.focusTrap.hasAttached()) {
446+
this._captureFocus();
447+
}
448+
}
449+
450+
private _captureFocus() {
451+
this._previouslyFocusedElement = this._document.activeElement as HTMLElement;
452+
this.focusTrap.focusInitialElementWhenReady();
453+
}
454+
439455
static ngAcceptInputType_enabled: BooleanInput;
440456
static ngAcceptInputType_autoCapture: BooleanInput;
441457
}

tools/public_api_guard/cdk/a11y.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export declare class CdkMonitorFocus implements AfterViewInit, OnDestroy {
4242
static ɵfac: i0.ɵɵFactoryDef<CdkMonitorFocus, never>;
4343
}
4444

45-
export declare class CdkTrapFocus implements OnDestroy, AfterContentInit, DoCheck {
45+
export declare class CdkTrapFocus implements OnDestroy, AfterContentInit, OnChanges, DoCheck {
4646
get autoCapture(): boolean;
4747
set autoCapture(value: boolean);
4848
get enabled(): boolean;
@@ -51,6 +51,7 @@ export declare class CdkTrapFocus implements OnDestroy, AfterContentInit, DoChec
5151
constructor(_elementRef: ElementRef<HTMLElement>, _focusTrapFactory: FocusTrapFactory, _document: any);
5252
ngAfterContentInit(): void;
5353
ngDoCheck(): void;
54+
ngOnChanges(changes: SimpleChanges): void;
5455
ngOnDestroy(): void;
5556
static ngAcceptInputType_autoCapture: BooleanInput;
5657
static ngAcceptInputType_enabled: BooleanInput;

0 commit comments

Comments
 (0)