mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(focus-trap): not attaching correctly if element is not in the DOM on init (#7665)
This commit is contained in:
committed by
Joey Perrott
parent
78f556a37b
commit
d64f94d6d1
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user