mirror of
https://github.com/bitwarden/browser
synced 2026-02-18 02:19:18 +00:00
86 lines
2.4 KiB
TypeScript
86 lines
2.4 KiB
TypeScript
// FIXME: Update this file to be type safe and remove this and next line
|
|
// @ts-strict-ignore
|
|
import { AfterContentChecked, Directive, ElementRef, Input, NgZone, Optional } from "@angular/core";
|
|
import { take } from "rxjs/operators";
|
|
|
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
|
|
|
import { FocusableElement } from "../shared/focusable-element";
|
|
|
|
/**
|
|
* Directive to focus an element.
|
|
*
|
|
* @remarks
|
|
*
|
|
* Will focus the element once, when it becomes visible.
|
|
*
|
|
* If the component provides the `FocusableElement` interface, the `focus`
|
|
* method will be called. Otherwise, the native element will be focused.
|
|
*/
|
|
@Directive({
|
|
selector: "[appAutofocus], [bitAutofocus]",
|
|
})
|
|
export class AutofocusDirective implements AfterContentChecked {
|
|
// TODO: Skipped for migration because:
|
|
// Accessor inputs cannot be migrated as they are too complex.
|
|
@Input() set appAutofocus(condition: boolean | string) {
|
|
this.autofocus = condition === "" || condition === true;
|
|
}
|
|
|
|
private autofocus: boolean;
|
|
|
|
// Track if we have already focused the element.
|
|
private focused = false;
|
|
|
|
constructor(
|
|
private el: ElementRef,
|
|
private ngZone: NgZone,
|
|
@Optional() private focusableElement: FocusableElement,
|
|
) {}
|
|
|
|
/**
|
|
* Using AfterContentChecked is a hack to ensure we only focus once. This is because
|
|
* the element may not be in the DOM, or not be focusable when the directive is
|
|
* created, and we want to wait until it is.
|
|
*
|
|
* Note: This might break in the future since it relies on Angular change detection
|
|
* to trigger after the element becomes visible.
|
|
*/
|
|
ngAfterContentChecked() {
|
|
// We only want to focus the element on initial render and it's not a mobile browser
|
|
if (this.focused || !this.autofocus || Utils.isMobileBrowser) {
|
|
return;
|
|
}
|
|
|
|
const el = this.getElement();
|
|
if (el == null) {
|
|
return;
|
|
}
|
|
|
|
if (this.ngZone.isStable) {
|
|
this.focus();
|
|
} else {
|
|
this.ngZone.onStable.pipe(take(1)).subscribe(this.focus.bind(this));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Attempt to focus the element. If successful we set focused to true to prevent further focus
|
|
* attempts.
|
|
*/
|
|
private focus() {
|
|
const el = this.getElement();
|
|
|
|
el.focus();
|
|
this.focused = el === document.activeElement;
|
|
}
|
|
|
|
private getElement() {
|
|
if (this.focusableElement) {
|
|
return this.focusableElement.getFocusTarget();
|
|
}
|
|
|
|
return this.el.nativeElement;
|
|
}
|
|
}
|