text: /**
   * @fileoverview added by tsickle
   * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc
   */
  /**
   * @license
   * Copyright Google LLC All Rights Reserved.
   *
   * Use of this source code is governed by an MIT-style license that can be
   * found in the LICENSE file at https://angular.io/license
   */
  import { QueryList } from '@angular/core';
  import { Subject, Subscription } from 'rxjs';
  import { UP_ARROW, DOWN_ARROW, LEFT_ARROW, RIGHT_ARROW, TAB, A, Z, ZERO, NINE, hasModifierKey, } from '@angular/cdk/keycodes';
  import { debounceTime, filter, map, tap } from 'rxjs/operators';
  /**
   * This interface is for items that can be passed to a ListKeyManager.
   * @record
   */
  export function ListKeyManagerOption() { }
  if (false) {
      /**
       * Whether the option is disabled.
       * @type {?|undefined}
       */
      ListKeyManagerOption.prototype.disabled;
      /**
       * Gets the label for this option.
       * @return {?}
       */
      ListKeyManagerOption.prototype.getLabel = function () { };
  }
  /**
   * This class manages keyboard events for selectable lists. If you pass it a query list
   * of items, it will set the active item correctly when arrow events occur.
   * @template T
   */
  export class ListKeyManager {
      /**
       * @param {?} _items
       */
      constructor(_items) {
          this._items = _items;
          this._activeItemIndex = -1;
          this._activeItem = null;
          this._wrap = false;
          this._letterKeyStream = new Subject();
          this._typeaheadSubscription = Subscription.EMPTY;
          this._vertical = true;
          this._allowedModifierKeys = [];
          /**
           * Predicate function that can be used to check whether an item should be skipped
           * by the key manager. By default, disabled items are skipped.
           */
          this._skipPredicateFn = (item) => item.disabled;
          // Buffer for the letters that the user has pressed when the typeahead option is turned on.
          this._pressedLetters = [];
          /**
           * Stream that emits any time the TAB key is pressed, so components can react
           * when focus is shifted off of the list.
           */
          this.tabOut = new Subject();
          /**
           * Stream that emits whenever the active item of the list manager changes.
           */
          this.change = new Subject();
          // We allow for the items to be an array because, in some cases, the consumer may
          // not have access to a QueryList of the items they want to manage (e.g. when the
          // items aren't being collected via `ViewChildren` or `ContentChildren`).
          if (_items instanceof QueryList) {
              _items.changes.subscribe((newItems) => {
                  if (this._activeItem) {
                      /** @type {?} */
                      const itemArray = newItems.toArray();
                      /** @type {?} */
                      const newIndex = itemArray.indexOf(this._activeItem);
                      if (newIndex > -1 && newIndex !== this._activeItemIndex) {
                          this._activeItemIndex = newIndex;
                      }
                  }
              });
          }
      }
      /**
       * Sets the predicate function that determines which items should be skipped by the
       * list key manager.
       * @template THIS
       * @this {THIS}
       * @param {?} predicate Function that determines whether the given item should be skipped.
       * @return {THIS}
       */
      skipPredicate(predicate) {
          (/** @type {?} */ (this))._skipPredicateFn = predicate;
          return (/** @type {?} */ (this));
      }
      /**
       * Configures wrapping mode, which determines whether the active item will wrap to
       * the other end of list when there are no more items in the given direction.
       * @template THIS
       * @this {THIS}
       * @param {?=} shouldWrap Whether the list should wrap when reaching the end.
       * @return {THIS}
       */
      withWrap(shouldWrap = true) {
          (/** @type {?} */ (this))._wrap = shouldWrap;
          return (/** @type {?} */ (this));
      }
      /**
       * Configures whether the key manager should be able to move the selection vertically.
       * @template THIS
       * @this {THIS}
       * @param {?=} enabled Whether vertical selection should be enabled.
       * @return {THIS}
       */
      withVerticalOrientation(enabled = true) {
          (/** @type {?} */ (this))._vertical = enabled;
          return (/** @type {?} */ (this));
      }
      /**
       * Configures the key manager to move the selection horizontally.
       * Passing in `null` will disable horizontal movement.
       * @template THIS
       * @this {THIS}
       * @param {?} direction Direction in which the selection can be moved.
       * @return {THIS}
       */
      withHorizontalOrientation(direction) {
          (/** @type {?} */ (this))._horizontal = direction;
          return (/** @type {?} */ (this));
      }
      /**
       * Modifier keys which are allowed to be held down and whose default actions will be prevented
       * as the user is pressing the arrow keys. Defaults to not allowing any modifier keys.
       * @template THIS
       * @this {THIS}
       * @param {?} keys
       * @return {THIS}
       */
      withAllowedModifierKeys(keys) {
          (/** @type {?} */ (this))._allowedModifierKeys = keys;
          return (/** @type {?} */ (this));
      }
      /**
       * Turns on typeahead mode which allows users to set the active item by typing.
       * @template THIS
       * @this {THIS}
       * @param {?=} debounceInterval Time to wait after the last keystroke before setting the active item.
       * @return {THIS}
       */
      withTypeAhead(debounceInterval = 200) {
          if ((/** @type {?} */ (this))._items.length && (/** @type {?} */ (this))._items.some(item => typeof item.getLabel !== 'function')) {
              throw Error('ListKeyManager items in typeahead mode must implement the `getLabel` method.');
          }
          (/** @type {?} */ (this))._typeaheadSubscription.unsubscribe();
          // Debounce the presses of non-navigational keys, collect the ones that correspond to letters
          // and convert those letters back into a string. Afterwards find the first item that starts
          // with that string and select it.
          (/** @type {?} */ (this))._typeaheadSubscription = (/** @type {?} */ (this))._letterKeyStream.pipe(tap(keyCode => (/** @type {?} */ (this))._pressedLetters.push(keyCode)), debounceTime(debounceInterval), filter(() => (/** @type {?} */ (this))._pressedLetters.length > 0), map(() => (/** @type {?} */ (this))._pressedLetters.join(''))).subscribe(inputString => {
              /** @type {?} */
              const items = (/** @type {?} */ (this))._getItemsArray();
              // Start at 1 because we want to start searching at the item immediately
              // following the current active item.
              for (let i = 1; i < items.length + 1; i++) {
                  /** @type {?} */
                  const index = ((/** @type {?} */ (this))._activeItemIndex + i) % items.length;
                  /** @type {?} */
                  const item = items[index];
                  if (!(/** @type {?} */ (this))._skipPredicateFn(item) &&
                      (/** @type {?} */ (item.getLabel))().toUpperCase().trim().indexOf(inputString) === 0) {
                      (/** @type {?} */ (this)).setActiveItem(index);
                      break;
                  }
              }
              (/** @type {?} */ (this))._pressedLetters = [];
          });
          return (/** @type {?} */ (this));
      }
      /**
       * @param {?} item
       * @return {?}
       */
      setActiveItem(item) {
          /** @type {?} */
          const previousIndex = this._activeItemIndex;
          this.updateActiveItem(item);
          if (this._activeItemIndex !== previousIndex) {
              this.change.next(this._activeItemIndex);
          }
      }
      /**
       * Sets the active item depending on the key event passed in.
       * @param {?} event Keyboard event to be used for determining which element should be active.
       * @return {?}
       */
      onKeydown(event) {
          /** @type {?} */
          const keyCode = event.keyCode;
          /** @type {?} */
          const modifiers = ['altKey', 'ctrlKey', 'metaKey', 'shiftKey'];
          /** @type {?} */
          const isModifierAllowed = modifiers.every(modifier => {
              return !event[modifier] || this._allowedModifierKeys.indexOf(modifier) > -1;
          });
          switch (keyCode) {
              case TAB:
                  this.tabOut.next();
                  return;
              case DOWN_ARROW:
                  if (this._vertical && isModifierAllowed) {
                      this.setNextItemActive();
                      break;
                  }
                  else {
                      return;
                  }
              case UP_ARROW:
                  if (this._vertical && isModifierAllowed) {
                      this.setPreviousItemActive();
                      break;
                  }
                  else {
                      return;
                  }
              case RIGHT_ARROW:
                  if (this._horizontal && isModifierAllowed) {
                      this._horizontal === 'rtl' ? this.setPreviousItemActive() : this.setNextItemActive();
                      break;
                  }
                  else {
                      return;
                  }
              case LEFT_ARROW:
                  if (this._horizontal && isModifierAllowed) {
                      this._horizontal === 'rtl' ? this.setNextItemActive() : this.setPreviousItemActive();
                      break;
                  }
                  else {
                      return;
                  }
              default:
                  if (isModifierAllowed || hasModifierKey(event, 'shiftKey')) {
                      // Attempt to use the `event.key` which also maps it to the user's keyboard language,
                      // otherwise fall back to resolving alphanumeric characters via the keyCode.
                      if (event.key && event.key.length === 1) {
                          this._letterKeyStream.next(event.key.toLocaleUpperCase());
                      }
                      else if ((keyCode >= A && keyCode <= Z) || (keyCode >= ZERO && keyCode <= NINE)) {
                          this._letterKeyStream.next(String.fromCharCode(keyCode));
                      }
                  }
                  // Note that we return here, in order to avoid preventing
                  // the default action of non-navigational keys.
                  return;
          }
          this._pressedLetters = [];
          event.preventDefault();
      }
      /**
       * Index of the currently active item.
       * @return {?}
       */
      get activeItemIndex() {
          return this._activeItemIndex;
      }
      /**
       * The active item.
       * @return {?}
       */
      get activeItem() {
          return this._activeItem;
      }
      /**
       * Sets the active item to the first enabled item in the list.
       * @return {?}
       */
      setFirstItemActive() {
          this._setActiveItemByIndex(0, 1);
      }
      /**
       * Sets the active item to the last enabled item in the list.
       * @return {?}
       */
      setLastItemActive() {
          this._setActiveItemByIndex(this._items.length - 1, -1);
      }
      /**
       * Sets the active item to the next enabled item in the list.
       * @return {?}
       */
      setNextItemActive() {
          this._activeItemIndex < 0 ? this.setFirstItemActive() : this._setActiveItemByDelta(1);
      }
      /**
       * Sets the active item to a previous enabled item in the list.
       * @return {?}
       */
      setPreviousItemActive() {
          this._activeItemIndex < 0 && this._wrap ? this.setLastItemActive()
              : this._setActiveItemByDelta(-1);
      }
      /**
       * @param {?} item
       * @return {?}
       */
      updateActiveItem(item) {
          /** @type {?} */
          const itemArray = this._getItemsArray();
          /** @type {?} */
          const index = typeof item === 'number' ? item : itemArray.indexOf(item);
          /** @type {?} */
          const activeItem = itemArray[index];
          // Explicitly check for `null` and `undefined` because other falsy values are valid.
          this._activeItem = activeItem == null ? null : activeItem;
          this._activeItemIndex = index;
      }
      /**
       * Allows setting of the activeItemIndex without any other effects.
       * @deprecated Use `updateActiveItem` instead.
       * \@breaking-change 8.0.0
       * @param {?} index The new activeItemIndex.
       * @return {?}
       */
      updateActiveItemIndex(index) {
          this.updateActiveItem(index);
      }
      /**
       * This method sets the active item, given a list of items and the delta between the
       * currently active item and the new active item. It will calculate differently
       * depending on whether wrap mode is turned on.
       * @private
       * @param {?} delta
       * @return {?}
       */
      _setActiveItemByDelta(delta) {
          this._wrap ? this._setActiveInWrapMode(delta) : this._setActiveInDefaultMode(delta);
      }
      /**
       * Sets the active item properly given "wrap" mode. In other words, it will continue to move
       * down the list until it finds an item that is not disabled, and it will wrap if it
       * encounters either end of the list.
       * @private
       * @param {?} delta
       * @return {?}
       */
      _setActiveInWrapMode(delta) {
          /** @type {?} */
          const items = this._getItemsArray();
          for (let i = 1; i <= items.length; i++) {
              /** @type {?} */
              const index = (this._activeItemIndex + (delta * i) + items.length) % items.length;
              /** @type {?} */
              const item = items[index];
              if (!this._skipPredicateFn(item)) {
                  this.setActiveItem(index);
                  return;
              }
          }
      }
      /**
       * Sets the active item properly given the default mode. In other words, it will
       * continue to move down the list until it finds an item that is not disabled. If
       * it encounters either end of the list, it will stop and not wrap.
       * @private
       * @param {?} delta
       * @return {?}
       */
      _setActiveInDefaultMode(delta) {
          this._setActiveItemByIndex(this._activeItemIndex + delta, delta);
      }
      /**
       * Sets the active item to the first enabled item starting at the index specified. If the
       * item is disabled, it will move in the fallbackDelta direction until it either
       * finds an enabled item or encounters the end of the list.
       * @private
       * @param {?} index
       * @param {?} fallbackDelta
       * @return {?}
       */
      _setActiveItemByIndex(index, fallbackDelta) {
          /** @type {?} */
          const items = this._getItemsArray();
          if (!items[index]) {
              return;
          }
          while (this._skipPredicateFn(items[index])) {
              index += fallbackDelta;
              if (!items[index]) {
                  return;
              }
          }
          this.setActiveItem(index);
      }
      /**
       * Returns the items as an array.
       * @private
       * @return {?}
       */
      _getItemsArray() {
          return this._items instanceof QueryList ? this._items.toArray() : this._items;
      }
  }
  if (false) {
      /**
       * @type {?}
       * @private
       */
      ListKeyManager.prototype._activeItemIndex;
      /**
       * @type {?}
       * @private
       */
      ListKeyManager.prototype._activeItem;
      /**
       * @type {?}
       * @private
       */
      ListKeyManager.prototype._wrap;
      /**
       * @type {?}
       * @private
       */
      ListKeyManager.prototype._letterKeyStream;
      /**
       * @type {?}
       * @private
       */
      ListKeyManager.prototype._typeaheadSubscription;
      /**
       * @type {?}
       * @private
       */
      ListKeyManager.prototype._vertical;
      /**
       * @type {?}
       * @private
       */
      ListKeyManager.prototype._horizontal;
      /**
       * @type {?}
       * @private
       */
      ListKeyManager.prototype._allowedModifierKeys;
      /**
       * Predicate function that can be used to check whether an item should be skipped
       * by the key manager. By default, disabled items are skipped.
       * @type {?}
       * @private
       */
      ListKeyManager.prototype._skipPredicateFn;
      /**
       * @type {?}
       * @private
       */
      ListKeyManager.prototype._pressedLetters;
      /**
       * Stream that emits any time the TAB key is pressed, so components can react
       * when focus is shifted off of the list.
       * @type {?}
       */
      ListKeyManager.prototype.tabOut;
      /**
       * Stream that emits whenever the active item of the list manager changes.
       * @type {?}
       */
      ListKeyManager.prototype.change;
      /**
       * @type {?}
       * @private
       */
      ListKeyManager.prototype._items;
  }
  //# sourceMappingURL=list-key-manager.js.map