Skip to content

Commit c74483b

Browse files
crisbetovictoriaaa234
authored andcommitted
fix(focus-trap): not attaching correctly if element is not in the DOM on init (#7665)
1 parent e8e3933 commit c74483b

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)