All files / lib/internal focus-scope.directive.ts

92.4% Statements 73/79
93.75% Branches 15/16
100% Functions 7/7
92.4% Lines 73/79

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 801x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1159x 1159x 1159x 1159x 1159x 1159x 1159x 1159x 1159x 1159x 1159x 1159x 1159x 1159x 1159x 1159x 1159x 1159x 1159x 1159x 1159x 1159x 1159x 1159x 1159x 3324x 3324x 1159x 1159x 1x 1x 345x 209x 209x 209x 209x 209x 209x 209x 209x 209x 209x             345x 1x 1x 577x 368x 368x 368x 577x 577x 1x 1x 922x 649x 1580x 922x 922x 922x 922x 922x 1x  
import {
  ContentChildren,
  Directive,
  EventEmitter,
  Output,
  QueryList,
  forwardRef,
} from '@angular/core';
 
import { FocusTargetDirective } from './focus-target.directive';
 
@Directive({
  selector: '[data-gc-focus-scope]',
})
export class FocusScopeDirective {
  @Output()
  public readonly onFocusLeft: EventEmitter<EventTarget | null> =
    new EventEmitter<EventTarget | null>();
 
  @Output()
  public readonly onFocusEntered: EventEmitter<void> = new EventEmitter<void>();
 
  // FocusTargetDirective gets the FocusScopeDirective injected in its constructor, hence
  // Angular will order the declaration of the FocusTargetDirective after the declaration
  // of the current class in the built library output. due to this, the FocusTargetDirective
  // is not yet declared at the point when the code/metadata for this decorator is emitted
  // and the class is missing => break this interdependency with a forwardRef, so the
  // FocusTargetDirective can be used before its declared.
  // note that the described error would be only visible at runtime when using the built library
  // in another project!
  @ContentChildren(forwardRef(() => FocusTargetDirective), {
    descendants: true,
  })
  protected focusTargets!: QueryList<FocusTargetDirective>;
 
  public get focusWithin(): boolean {
    return this._focusWithin;
  }
 
  private _focusWithin = false;
 
  public onTargetBlur(event: FocusEvent): void {
    const newFocusedElement = event.relatedTarget;
 
    if (!this.isFocusWithin(newFocusedElement)) {
      this._focusWithin = false;
      this.onFocusLeft.emit(event.relatedTarget);
    } else if (
      event.relatedTarget &&
      (event.relatedTarget as HTMLElement).getAttribute(
        'data-gc-refuse-focus',
      ) === 'true'
    ) {
      // Workaround for https://gitlab.bestsolution.at/gefa/gefa-web-controls/-/issues/2670
      const el = event.target as HTMLElement;
      setTimeout(() => {
        el.focus();
      });
    }
  }
 
  public onTargetFocus(event: FocusEvent): void {
    const previousFocusedElement = event.relatedTarget;
    if (!this.isFocusWithin(previousFocusedElement)) {
      this._focusWithin = true;
      this.onFocusEntered.emit();
    }
  }
 
  private isFocusWithin(eventTarget: EventTarget | null): boolean {
    if (eventTarget !== null) {
      return this.focusTargets.some(target =>
        target.isFocusTarget(eventTarget),
      );
    } else {
      return false;
    }
  }
}