graycoreio/daffodil

View on GitHub
libs/design/src/core/focus/stack.service.ts

Summary

Maintainability
A
0 mins
Test Coverage
import { DOCUMENT } from '@angular/common';
import {
  Inject,
  Injectable,
} from '@angular/core';

@Injectable({ providedIn: 'root' })
export class DaffFocusStackService {
  private _stack: HTMLElement[] = [];

  constructor(@Inject(DOCUMENT) private document: any) {

  }

  /**
   * Return the current length of the stack.
   */
  length(): number {
    return this._stack.length;
  }

  /**
   * Adds a HTML element to a focus stack and returns the new length of the stack.
   *
   * Generally, you will probably want to call this before you transition focus
   * onto a new element.
   *
   * ```ts
   * this._focusStack.push(this._doc.activeElement);
   * ```
   */
  push(el: HTMLElement | undefined = undefined): number {
    this._stack.push(el ?? this.document.activeElement);
    return this._stack.length;
  }

  /**
   * Focuses on the HTML element at the top of a stack.
   *
   * ```ts
   * this._focusStack.push(this._doc.activeElement);
   * ```
   */
  focus() {
    if(this._stack.length >= 1) {
      this._stack.slice(-1)[0].focus();
    } else {
      (<HTMLElement>this.document.activeElement).blur();
    }
  }

  /**
   * Removes the HMTL element at the top of a stack and focuses on it.
   */
  pop(focus: boolean = true): HTMLElement {
    let el = this._stack.pop();
    while(el === undefined && this._stack.length > 0) {
      el = this._stack.pop();
    }

    if(el) {
      if(focus) {
        el.focus();
      }
      return el;
    }

    (<HTMLElement>this.document.activeElement).blur();
    return this.document.activeElement;
  }
}