Skip to content

Commit 560f1b6

Browse files
crisbetojelbourn
authored andcommitted
refactor(focus-trap): better handling of server-side rendering (#8519)
Now that Angular is using Domino, we don't have to completely shut off the focus trap when rendering on the server. These changes remove all the old checks and use the correct document injection token.
1 parent f6cb2c6 commit 560f1b6

File tree

2 files changed

+25
-22
lines changed

2 files changed

+25
-22
lines changed

src/cdk/a11y/focus-trap.ts

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@ import {
1414
OnDestroy,
1515
AfterContentInit,
1616
Injectable,
17+
Inject,
1718
} from '@angular/core';
1819
import {coerceBooleanProperty} from '@angular/cdk/coercion';
19-
import {Platform} from '@angular/cdk/platform';
2020
import {first} from 'rxjs/operators/first';
2121
import {InteractivityChecker} from './interactivity-checker';
22+
import {DOCUMENT} from '@angular/common';
2223

2324

2425
/**
@@ -45,9 +46,9 @@ export class FocusTrap {
4546

4647
constructor(
4748
private _element: HTMLElement,
48-
private _platform: Platform,
4949
private _checker: InteractivityChecker,
5050
private _ngZone: NgZone,
51+
private _document: Document,
5152
deferAnchors = false) {
5253

5354
if (!deferAnchors) {
@@ -73,11 +74,6 @@ export class FocusTrap {
7374
* in the constructor, but can be deferred for cases like directives with `*ngIf`.
7475
*/
7576
attachAnchors(): void {
76-
// If we're not on the browser, there can be no focus to trap.
77-
if (!this._platform.isBrowser) {
78-
return;
79-
}
80-
8177
if (!this._startAnchor) {
8278
this._startAnchor = this._createAnchor();
8379
}
@@ -144,10 +140,6 @@ export class FocusTrap {
144140
* @returns The boundary element.
145141
*/
146142
private _getRegionBoundary(bound: 'start' | 'end'): HTMLElement | null {
147-
if (!this._platform.isBrowser) {
148-
return null;
149-
}
150-
151143
// Contains the deprecated version of selector, for temporary backwards comparability.
152144
let markers = this._element.querySelectorAll(`[cdk-focus-region-${bound}], ` +
153145
`[cdkFocusRegion${bound}], ` +
@@ -175,10 +167,6 @@ export class FocusTrap {
175167
* @returns Whether focus was moved successfuly.
176168
*/
177169
focusInitialElement(): boolean {
178-
if (!this._platform.isBrowser) {
179-
return false;
180-
}
181-
182170
// Contains the deprecated version of selector, for temporary backwards comparability.
183171
const redirectToElement = this._element.querySelector(`[cdk-focus-initial], ` +
184172
`[cdkFocusInitial]`) as HTMLElement;
@@ -271,7 +259,7 @@ export class FocusTrap {
271259

272260
/** Creates an anchor element. */
273261
private _createAnchor(): HTMLElement {
274-
let anchor = document.createElement('div');
262+
const anchor = this._document.createElement('div');
275263
anchor.tabIndex = this._enabled ? 0 : -1;
276264
anchor.classList.add('cdk-visually-hidden');
277265
anchor.classList.add('cdk-focus-trap-anchor');
@@ -292,10 +280,15 @@ export class FocusTrap {
292280
/** Factory that allows easy instantiation of focus traps. */
293281
@Injectable()
294282
export class FocusTrapFactory {
283+
private _document: Document;
284+
295285
constructor(
296286
private _checker: InteractivityChecker,
297-
private _platform: Platform,
298-
private _ngZone: NgZone) { }
287+
private _ngZone: NgZone,
288+
@Inject(DOCUMENT) _document: any) {
289+
290+
this._document = _document;
291+
}
299292

300293
/**
301294
* Creates a focus-trapped region around the given element.
@@ -306,7 +299,7 @@ export class FocusTrapFactory {
306299
*/
307300
create(element: HTMLElement, deferCaptureElements: boolean = false): FocusTrap {
308301
return new FocusTrap(
309-
element, this._platform, this._checker, this._ngZone, deferCaptureElements);
302+
element, this._checker, this._ngZone, this._document, deferCaptureElements);
310303
}
311304
}
312305

@@ -349,6 +342,8 @@ export class FocusTrapDeprecatedDirective implements OnDestroy, AfterContentInit
349342
exportAs: 'cdkTrapFocus',
350343
})
351344
export class CdkTrapFocus implements OnDestroy, AfterContentInit {
345+
private _document: Document;
346+
352347
/** Underlying FocusTrap instance. */
353348
focusTrap: FocusTrap;
354349

@@ -372,7 +367,9 @@ export class CdkTrapFocus implements OnDestroy, AfterContentInit {
372367
constructor(
373368
private _elementRef: ElementRef,
374369
private _focusTrapFactory: FocusTrapFactory,
375-
private _platform: Platform) {
370+
@Inject(DOCUMENT) _document: any) {
371+
372+
this._document = _document;
376373
this.focusTrap = this._focusTrapFactory.create(this._elementRef.nativeElement, true);
377374
}
378375

@@ -390,8 +387,8 @@ export class CdkTrapFocus implements OnDestroy, AfterContentInit {
390387
ngAfterContentInit() {
391388
this.focusTrap.attachAnchors();
392389

393-
if (this.autoCapture && this._platform.isBrowser) {
394-
this._previouslyFocusedElement = document.activeElement as HTMLElement;
390+
if (this.autoCapture) {
391+
this._previouslyFocusedElement = this._document.activeElement as HTMLElement;
395392
this.focusTrap.focusInitialElementWhenReady();
396393
}
397394
}

src/universal-app/kitchen-sink/kitchen-sink.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,3 +313,9 @@ <h2>Horizontal Stepper</h2>
313313
<mat-step label="Step 1">Content 1</mat-step>
314314
<mat-step label="Step 2">Content 2</mat-step>
315315
</mat-horizontal-stepper>
316+
317+
<h2>Focus trap</h2>
318+
319+
<div cdkTrapFocus cdkTrapFocusAutoCapture>
320+
<button>Oh no, I'm trapped!</button>
321+
</div>

0 commit comments

Comments
 (0)