fix(focus-trap): not attaching correctly if element is not in the DOM on init (#7665)

This commit is contained in:
Kristiyan Kostadinov
2018-06-29 23:03:50 +02:00
committed by Joey Perrott
parent 78f556a37b
commit d64f94d6d1
+37 -18
View File
@@ -17,6 +17,7 @@ import {
Input,
NgZone,
OnDestroy,
DoCheck,
} from '@angular/core';
import {take} from 'rxjs/operators';
import {InteractivityChecker} from '../interactivity-checker/interactivity-checker';
@@ -32,6 +33,7 @@ import {InteractivityChecker} from '../interactivity-checker/interactivity-check
export class FocusTrap {
private _startAnchor: HTMLElement | null;
private _endAnchor: HTMLElement | null;
private _hasAttached = false;
/** Whether the focus trap is active. */
get enabled(): boolean { return this._enabled; }
@@ -72,30 +74,34 @@ export class FocusTrap {
/**
* Inserts the anchors into the DOM. This is usually done automatically
* in the constructor, but can be deferred for cases like directives with `*ngIf`.
* @returns Whether the focus trap managed to attach successfuly. This may not be the case
* if the target element isn't currently in the DOM.
*/
attachAnchors(): void {
if (!this._startAnchor) {
this._startAnchor = this._createAnchor();
}
if (!this._endAnchor) {
this._endAnchor = this._createAnchor();
attachAnchors(): boolean {
// If we're not on the browser, there can be no focus to trap.
if (this._hasAttached) {
return true;
}
this._ngZone.runOutsideAngular(() => {
this._startAnchor!.addEventListener('focus', () => {
this.focusLastTabbableElement();
});
if (!this._startAnchor) {
this._startAnchor = this._createAnchor();
this._startAnchor!.addEventListener('focus', () => this.focusLastTabbableElement());
}
this._endAnchor!.addEventListener('focus', () => {
this.focusFirstTabbableElement();
});
if (this._element.parentNode) {
this._element.parentNode.insertBefore(this._startAnchor!, this._element);
this._element.parentNode.insertBefore(this._endAnchor!, this._element.nextSibling);
if (!this._endAnchor) {
this._endAnchor = this._createAnchor();
this._endAnchor!.addEventListener('focus', () => this.focusFirstTabbableElement());
}
});
if (this._element.parentNode) {
this._element.parentNode.insertBefore(this._startAnchor!, this._element);
this._element.parentNode.insertBefore(this._endAnchor!, this._element.nextSibling);
this._hasAttached = true;
}
return this._hasAttached;
}
/**
@@ -217,6 +223,13 @@ export class FocusTrap {
return !!redirectToElement;
}
/**
* Checks whether the focus trap has successfuly been attached.
*/
hasAttached(): boolean {
return this._hasAttached;
}
/** Get the first tabbable element from a DOM subtree (inclusive). */
private _getFirstTabbableElement(root: HTMLElement): HTMLElement | null {
if (this._checker.isFocusable(root) && this._checker.isTabbable(root)) {
@@ -313,7 +326,7 @@ export class FocusTrapFactory {
selector: '[cdkTrapFocus]',
exportAs: 'cdkTrapFocus',
})
export class CdkTrapFocus implements OnDestroy, AfterContentInit {
export class CdkTrapFocus implements OnDestroy, AfterContentInit, DoCheck {
private _document: Document;
/** Underlying FocusTrap instance. */
@@ -364,4 +377,10 @@ export class CdkTrapFocus implements OnDestroy, AfterContentInit {
this.focusTrap.focusInitialElementWhenReady();
}
}
ngDoCheck() {
if (!this.focusTrap.hasAttached()) {
this.focusTrap.attachAnchors();
}
}
}