Skip to content

Commit f01e4bb

Browse files
committed
fix(focus-trap): not attaching correctly if element is not in the DOM on init
Currently the focus trap attempts to attach itself once on init (e.g. when it is projected inside of an overlay), however in some cases it might not be in the DOM yet. These changes make it so it re-tries to attach itself until it does so successfully.
1 parent ae0430a commit f01e4bb

File tree

1 file changed

+37
-18
lines changed

1 file changed

+37
-18
lines changed

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

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
Input,
1818
NgZone,
1919
OnDestroy,
20+
DoCheck,
2021
} from '@angular/core';
2122
import {take} from 'rxjs/operators';
2223
import {InteractivityChecker} from '../interactivity-checker/interactivity-checker';
@@ -32,6 +33,7 @@ import {InteractivityChecker} from '../interactivity-checker/interactivity-check
3233
export class FocusTrap {
3334
private _startAnchor: HTMLElement | null;
3435
private _endAnchor: HTMLElement | null;
36+
private _hasAttached = false;
3537

3638
/** Whether the focus trap is active. */
3739
get enabled(): boolean { return this._enabled; }
@@ -72,30 +74,34 @@ export class FocusTrap {
7274
/**
7375
* Inserts the anchors into the DOM. This is usually done automatically
7476
* in the constructor, but can be deferred for cases like directives with `*ngIf`.
77+
* @returns Whether the focus trap managed to attach successfuly. This may not be the case
78+
* if the target element isn't currently in the DOM.
7579
*/
76-
attachAnchors(): void {
77-
if (!this._startAnchor) {
78-
this._startAnchor = this._createAnchor();
79-
}
80-
81-
if (!this._endAnchor) {
82-
this._endAnchor = this._createAnchor();
80+
attachAnchors(): boolean {
81+
// If we're not on the browser, there can be no focus to trap.
82+
if (this._hasAttached) {
83+
return true;
8384
}
8485

8586
this._ngZone.runOutsideAngular(() => {
86-
this._startAnchor!.addEventListener('focus', () => {
87-
this.focusLastTabbableElement();
88-
});
89-
90-
this._endAnchor!.addEventListener('focus', () => {
91-
this.focusFirstTabbableElement();
92-
});
87+
if (!this._startAnchor) {
88+
this._startAnchor = this._createAnchor();
89+
this._startAnchor!.addEventListener('focus', () => this.focusLastTabbableElement());
90+
}
9391

94-
if (this._element.parentNode) {
95-
this._element.parentNode.insertBefore(this._startAnchor!, this._element);
96-
this._element.parentNode.insertBefore(this._endAnchor!, this._element.nextSibling);
92+
if (!this._endAnchor) {
93+
this._endAnchor = this._createAnchor();
94+
this._endAnchor!.addEventListener('focus', () => this.focusFirstTabbableElement());
9795
}
9896
});
97+
98+
if (this._element.parentNode) {
99+
this._element.parentNode.insertBefore(this._startAnchor!, this._element);
100+
this._element.parentNode.insertBefore(this._endAnchor!, this._element.nextSibling);
101+
this._hasAttached = true;
102+
}
103+
104+
return this._hasAttached;
99105
}
100106

101107
/**
@@ -217,6 +223,13 @@ export class FocusTrap {
217223
return !!redirectToElement;
218224
}
219225

226+
/**
227+
* Checks whether the focus trap has successfuly been attached.
228+
*/
229+
hasAttached(): boolean {
230+
return this._hasAttached;
231+
}
232+
220233
/** Get the first tabbable element from a DOM subtree (inclusive). */
221234
private _getFirstTabbableElement(root: HTMLElement): HTMLElement | null {
222235
if (this._checker.isFocusable(root) && this._checker.isTabbable(root)) {
@@ -313,7 +326,7 @@ export class FocusTrapFactory {
313326
selector: '[cdkTrapFocus]',
314327
exportAs: 'cdkTrapFocus',
315328
})
316-
export class CdkTrapFocus implements OnDestroy, AfterContentInit {
329+
export class CdkTrapFocus implements OnDestroy, AfterContentInit, DoCheck {
317330
private _document: Document;
318331

319332
/** Underlying FocusTrap instance. */
@@ -364,4 +377,10 @@ export class CdkTrapFocus implements OnDestroy, AfterContentInit {
364377
this.focusTrap.focusInitialElementWhenReady();
365378
}
366379
}
380+
381+
ngDoCheck() {
382+
if (!this.focusTrap.hasAttached()) {
383+
this.focusTrap.attachAnchors();
384+
}
385+
}
367386
}

0 commit comments

Comments
 (0)