text: "use strict";
  Object.defineProperty(exports, "__esModule", { value: true });
  var tslib_1 = require("tslib");
  var React = require("react");
  var index_1 = require("../Autofill/index");
  var Utilities_1 = require("../../Utilities");
  var Callout_1 = require("../../Callout");
  var Checkbox_1 = require("../../Checkbox");
  var Button_1 = require("../../Button");
  var DirectionalHint_1 = require("../../common/DirectionalHint");
  var ComboBox_styles_1 = require("./ComboBox.styles");
  var ComboBox_classNames_1 = require("./ComboBox.classNames");
  var KeytipData_1 = require("../../KeytipData");
  var Label_1 = require("../../Label");
  var index_2 = require("../../utilities/selectableOption/index");
  var SearchDirection;
  (function (SearchDirection) {
      SearchDirection[SearchDirection["backward"] = -1] = "backward";
      SearchDirection[SearchDirection["none"] = 0] = "none";
      SearchDirection[SearchDirection["forward"] = 1] = "forward";
  })(SearchDirection || (SearchDirection = {}));
  var HoverStatus;
  (function (HoverStatus) {
      /** Used when the user was hovering and has since moused out of the menu items */
      HoverStatus[HoverStatus["clearAll"] = -2] = "clearAll";
      /** Default "normal" state, when no hover has happened or a hover is in progress */
      HoverStatus[HoverStatus["default"] = -1] = "default";
  })(HoverStatus || (HoverStatus = {}));
  var ScrollIdleDelay = 250 /* ms */;
  var TouchIdleDelay = 500; /* ms */
  /**
   * This is used to clear any pending autocomplete text (used when autocomplete is true and
   * allowFreeform is false)
   */
  var ReadOnlyPendingAutoCompleteTimeout = 1000 /* ms */;
  /**
   * Internal class that is used to wrap all ComboBox options.
   * This is used to customize when we want to rerender components,
   * so we don't rerender every option every time render is executed.
   */
  var ComboBoxOptionWrapper = /** @class */ (function (_super) {
      tslib_1.__extends(ComboBoxOptionWrapper, _super);
      function ComboBoxOptionWrapper() {
          return _super !== null && _super.apply(this, arguments) || this;
      }
      ComboBoxOptionWrapper.prototype.render = function () {
          return this.props.render();
      };
      ComboBoxOptionWrapper.prototype.shouldComponentUpdate = function (newProps) {
          // The render function will always be different, so we ignore that prop
          return !Utilities_1.shallowCompare(tslib_1.__assign({}, this.props, { render: undefined }), tslib_1.__assign({}, newProps, { render: undefined }));
      };
      return ComboBoxOptionWrapper;
  }(React.Component));
  var ComboBox = /** @class */ (function (_super) {
      tslib_1.__extends(ComboBox, _super);
      function ComboBox(props) {
          var _this = _super.call(this, props) || this;
          _this._root = React.createRef();
          /** The input aspect of the comboBox */
          _this._autofill = React.createRef();
          /** The wrapping div of the input and button */
          _this._comboBoxWrapper = React.createRef();
          /** The callout element */
          _this._comboBoxMenu = React.createRef();
          /** The menu item element that is currently selected */
          _this._selectedElement = React.createRef();
          /**
           * {@inheritdoc}
           */
          _this.focus = function (shouldOpenOnFocus, useFocusAsync) {
              if (_this._autofill.current) {
                  if (useFocusAsync) {
                      Utilities_1.focusAsync(_this._autofill.current);
                  }
                  else {
                      _this._autofill.current.focus();
                  }
                  if (shouldOpenOnFocus) {
                      _this.setState({
                          isOpen: true
                      });
                  }
              }
          };
          /**
           * Close menu callout if it is open
           */
          _this.dismissMenu = function () {
              var isOpen = _this.state.isOpen;
              isOpen && _this.setState({ isOpen: false });
          };
          /**
           * componentWillReceiveProps handler for the auto fill component
           * Checks/updates the iput value to set, if needed
           * @param defaultVisibleValue - the defaultVisibleValue that got passed
           *  in to the auto fill's componentWillReceiveProps
           * @returns - the updated value to set, if needed
           */
          _this._onUpdateValueInAutofillWillReceiveProps = function () {
              var comboBox = _this._autofill.current;
              if (!comboBox) {
                  return null;
              }
              if (comboBox.value === null || comboBox.value === undefined) {
                  return null;
              }
              var visibleValue = _this._normalizeToString(_this._currentVisibleValue);
              if (comboBox.value !== visibleValue) {
                  // If visibleValue is empty, make it a zero width space.
                  // If we did not do that, the empty string would not get used
                  // potentially resulting in an unexpected value being used
                  return visibleValue || '​';
              }
              return comboBox.value;
          };
          /**
           * componentDidUpdate handler for the auto fill component
           *
           * @param defaultVisibleValue - the current defaultVisibleValue in the auto fill's componentDidUpdate
           * @param suggestedDisplayValue - the current suggestedDisplayValue in the auto fill's componentDidUpdate
           * @returns - should the full value of the input be selected?
           * True if the defaultVisibleValue equals the suggestedDisplayValue, false otherwise
           */
          _this._onShouldSelectFullInputValueInAutofillComponentDidUpdate = function () {
              return _this._currentVisibleValue === _this.state.suggestedDisplayValue;
          };
          /**
           * Get the correct value to pass to the input
           * to show to the user based off of the current props and state
           * @returns the value to pass to the input
           */
          _this._getVisibleValue = function () {
              var _a = _this.props, text = _a.text, allowFreeform = _a.allowFreeform, autoComplete = _a.autoComplete;
              var _b = _this.state, selectedIndices = _b.selectedIndices, currentPendingValueValidIndex = _b.currentPendingValueValidIndex, currentOptions = _b.currentOptions, currentPendingValue = _b.currentPendingValue, suggestedDisplayValue = _b.suggestedDisplayValue, isOpen = _b.isOpen, focused = _b.focused;
              var currentPendingIndexValid = _this._indexWithinBounds(currentOptions, currentPendingValueValidIndex);
              // If the user passed is a value prop, use that
              // unless we are open and have a valid current pending index
              if (!(isOpen && currentPendingIndexValid) && (text && (currentPendingValue === null || currentPendingValue === undefined))) {
                  return text;
              }
              // Values to display in the BaseAutoFill area
              var displayValues = [];
              if (_this.props.multiSelect) {
                  // Multi-select
                  if (focused) {
                      var index = -1;
                      if (autoComplete === 'on' && currentPendingIndexValid) {
                          index = currentPendingValueValidIndex;
                      }
                      displayValues.push(currentPendingValue !== null && currentPendingValue !== undefined
                          ? currentPendingValue
                          : _this._indexWithinBounds(currentOptions, index)
                              ? currentOptions[index].text
                              : '');
                  }
                  else {
                      for (var idx = 0; selectedIndices && idx < selectedIndices.length; idx++) {
                          var index = selectedIndices[idx];
                          displayValues.push(_this._indexWithinBounds(currentOptions, index) ? currentOptions[index].text : _this._normalizeToString(suggestedDisplayValue));
                      }
                  }
              }
              else {
                  // Single-select
                  var index = _this._getFirstSelectedIndex();
                  if (allowFreeform) {
                      // If we are allowing freeform and autocomplete is also true
                      // and we've got a pending value that matches an option, remember
                      // the matched option's index
                      if (autoComplete === 'on' && currentPendingIndexValid) {
                          index = currentPendingValueValidIndex;
                      }
                      // Since we are allowing freeform, if there is currently a pending value, use that
                      // otherwise use the index determined above (falling back to '' if we did not get a valid index)
                      displayValues.push(currentPendingValue !== null && currentPendingValue !== undefined
                          ? currentPendingValue
                          : _this._indexWithinBounds(currentOptions, index)
                              ? currentOptions[index].text
                              : '');
                  }
                  else {
                      // If we are not allowing freeform and have a
                      // valid index that matches the pending value,
                      // we know we will need some version of the pending value
                      if (currentPendingIndexValid && autoComplete === 'on') {
                          // If autoComplete is on, return the
                          // raw pending value, otherwise remember
                          // the matched option's index
                          index = currentPendingValueValidIndex;
                          displayValues.push(_this._normalizeToString(currentPendingValue));
                      }
                      else {
                          displayValues.push(_this._indexWithinBounds(currentOptions, index) ? currentOptions[index].text : _this._normalizeToString(suggestedDisplayValue));
                      }
                  }
              }
              // If we have a valid index then return the text value of that option,
              // otherwise return the suggestedDisplayValue
              var displayString = '';
              for (var idx = 0; idx < displayValues.length; idx++) {
                  if (idx > 0) {
                      displayString += ', ';
                  }
                  displayString += displayValues[idx];
              }
              return displayString;
          };
          /**
           * Handler for typing changes on the input
           * @param updatedValue - the newly changed value
           */
          _this._onInputChange = function (updatedValue) {
              if (_this.props.disabled) {
                  _this._handleInputWhenDisabled(null /* event */);
                  return;
              }
              _this.props.allowFreeform ? _this._processInputChangeWithFreeform(updatedValue) : _this._processInputChangeWithoutFreeform(updatedValue);
          };
          /**
           * Focus (and select) the content of the input
           * and set the focused state
           */
          _this._select = function () {
              if (_this._autofill.current && _this._autofill.current.inputElement) {
                  _this._autofill.current.inputElement.select();
              }
              if (!_this.state.focused) {
                  var state = { focused: true };
                  if (_this.props.openOnKeyboardFocus && !_this.state.isOpen && !_this.props.disabled) {
                      state.isOpen = true;
                  }
                  _this.setState(state);
              }
          };
          /**
           * Callback issued when the options should be resolved, if they have been updated or
           * if they need to be passed in the first time. This only does work if an onResolveOptions
           * callback was passed in
           */
          _this._onResolveOptions = function () {
              if (_this.props.onResolveOptions) {
                  // get the options
                  var newOptions = _this.props.onResolveOptions(_this.state.currentOptions.slice());
                  // Check to see if the returned value is an array, if it is update the state
                  // If the returned value is not an array then check to see if it's a promise or PromiseLike. If it is then resolve it asynchronously.
                  if (Array.isArray(newOptions)) {
                      _this.setState({
                          currentOptions: newOptions
                      });
                  }
                  else if (newOptions && newOptions.then) {
                      // Ensure that the promise will only use the callback if it was the most recent one
                      // and update the state when the promise returns
                      var promise_1 = (_this._currentPromise = newOptions);
                      promise_1.then(function (newOptionsFromPromise) {
                          if (promise_1 === _this._currentPromise) {
                              _this.setState({
                                  currentOptions: newOptionsFromPromise
                              });
                          }
                      });
                  }
              }
          };
          /**
           * OnBlur handler. Set the focused state to false
           * and submit any pending value
           */
          _this._onBlur = function (event) {
              // Do nothing if the blur is coming from something
              // inside the comboBox root or the comboBox menu since
              // it we are not really bluring from the whole comboBox
              var relatedTarget = event.relatedTarget;
              if (event.relatedTarget === null) {
                  // In IE11, due to lack of support, event.relatedTarget is always
                  // null making every onBlur call to be "outside" of the ComboBox
                  // even when it's not. Using document.activeElement is another way
                  // for us to be able to get what the relatedTarget without relying
                  // on the event
                  relatedTarget = document.activeElement;
              }
              if (relatedTarget &&
                  // when event coming from withing the comboBox title
                  ((_this._root.current && _this._root.current.contains(relatedTarget)) ||
                      // when event coming from within the comboBox list menu
                      (_this._comboBoxMenu.current &&
                          (_this._comboBoxMenu.current.contains(relatedTarget) ||
                              // when event coming from the callout containing the comboBox list menu (ex: when scrollBar of the Callout clicked)
                              // checks if the relatedTarget is a parent of _comboBoxMenu
                              Utilities_1.findElementRecursive(_this._comboBoxMenu.current, function (element) { return element === relatedTarget; }))))) {
                  event.preventDefault();
                  event.stopPropagation();
                  return;
              }
              if (_this.state.focused) {
                  _this.setState({ focused: false });
                  if (!_this.props.multiSelect || _this.props.allowFreeform) {
                      _this._submitPendingValue(event);
                  }
              }
          };
          // Render Callout container and pass in list
          _this._onRenderContainer = function (props) {
              var onRenderList = props.onRenderList, calloutProps = props.calloutProps, dropdownWidth = props.dropdownWidth, dropdownMaxWidth = props.dropdownMaxWidth, _a = props.onRenderLowerContent, onRenderLowerContent = _a === void 0 ? _this._onRenderLowerContent : _a, useComboBoxAsMenuWidth = props.useComboBoxAsMenuWidth, persistMenu = props.persistMenu, _b = props.shouldRestoreFocus, shouldRestoreFocus = _b === void 0 ? true : _b;
              var isOpen = _this.state.isOpen;
              var comboBoxMenuWidth = useComboBoxAsMenuWidth && _this._comboBoxWrapper.current ? _this._comboBoxWrapper.current.clientWidth + 2 : undefined;
              return (React.createElement(Callout_1.Callout, tslib_1.__assign({ isBeakVisible: false, gapSpace: 0, doNotLayer: false, directionalHint: DirectionalHint_1.DirectionalHint.bottomLeftEdge, directionalHintFixed: false }, calloutProps, { onLayerMounted: _this._onLayerMounted, className: Utilities_1.css(_this._classNames.callout, calloutProps ? calloutProps.className : undefined), target: _this._comboBoxWrapper.current, onDismiss: _this._onDismiss, onScroll: _this._onScroll, setInitialFocus: false, calloutWidth: useComboBoxAsMenuWidth && _this._comboBoxWrapper.current ? comboBoxMenuWidth && comboBoxMenuWidth : dropdownWidth, calloutMaxWidth: dropdownMaxWidth ? dropdownMaxWidth : comboBoxMenuWidth, hidden: persistMenu ? !isOpen : undefined, shouldRestoreFocus: shouldRestoreFocus }),
                  React.createElement("div", { className: _this._classNames.optionsContainerWrapper, ref: _this._comboBoxMenu }, onRenderList(tslib_1.__assign({}, props), _this._onRenderList)),
                  onRenderLowerContent(_this.props, _this._onRenderLowerContent)));
          };
          _this._onLayerMounted = function () {
              _this._onCalloutLayerMounted();
              if (_this.props.calloutProps && _this.props.calloutProps.onLayerMounted) {
                  _this.props.calloutProps.onLayerMounted();
              }
          };
          // Render List of items
          _this._onRenderList = function (props) {
              var onRenderItem = props.onRenderItem, options = props.options;
              var id = _this._id;
              return (React.createElement("div", { id: id + '-list', className: _this._classNames.optionsContainer, "aria-labelledby": id + '-label', role: "listbox" }, options.map(function (item) { return onRenderItem(item, _this._onRenderItem); })));
          };
          // Render items
          _this._onRenderItem = function (item) {
              switch (item.itemType) {
                  case index_2.SelectableOptionMenuItemType.Divider:
                      return _this._renderSeparator(item);
                  case index_2.SelectableOptionMenuItemType.Header:
                      return _this._renderHeader(item);
                  default:
                      return _this._renderOption(item);
              }
          };
          // Default _onRenderLowerContent function returns nothing
          _this._onRenderLowerContent = function () {
              return null;
          };
          _this._renderOption = function (item) {
              var _a = _this.props.onRenderOption, onRenderOption = _a === void 0 ? _this._onRenderOptionContent : _a;
              var id = _this._id;
              var isSelected = _this._isOptionSelected(item.index);
              var optionStyles = _this._getCurrentOptionStyles(item);
              var optionClassNames = ComboBox_classNames_1.getComboBoxOptionClassNames(_this._getCurrentOptionStyles(item));
              var checkboxStyles = function () {
                  return optionStyles;
              };
              var title = _this._getPreviewText(item);
              var getOptionComponent = function () {
                  return !_this.props.multiSelect ? (React.createElement(Button_1.CommandButton, { id: id + '-list' + item.index, key: item.key, "data-index": item.index, styles: _this._getCurrentOptionStyles(item), checked: isSelected, className: 'ms-ComboBox-option', onClick: _this._onItemClick(item), onMouseEnter: _this._onOptionMouseEnter.bind(_this, item.index), onMouseMove: _this._onOptionMouseMove.bind(_this, item.index), onMouseLeave: _this._onOptionMouseLeave, role: "option", "aria-selected": isSelected ? 'true' : 'false', ariaLabel: _this._getPreviewText(item), disabled: item.disabled, title: title },
                      ' ',
                      React.createElement("span", { className: optionClassNames.optionTextWrapper, ref: isSelected ? _this._selectedElement : undefined }, onRenderOption(item, _this._onRenderOptionContent)))) : (React.createElement(Checkbox_1.Checkbox, { id: id + '-list' + item.index, ariaLabel: _this._getPreviewText(item), key: item.key, "data-index": item.index, styles: checkboxStyles, className: 'ms-ComboBox-option', "data-is-focusable": true, onChange: _this._onItemClick(item), label: item.text, role: "option", "aria-selected": isSelected ? 'true' : 'false', checked: isSelected, title: title, disabled: item.disabled }, onRenderOption(item, _this._onRenderOptionContent)));
              };
              return (React.createElement(ComboBoxOptionWrapper, { key: item.key, index: item.index, disabled: item.disabled, isSelected: isSelected, text: item.text, render: getOptionComponent, data: item.data }));
          };
          /**
           * Scroll handler for the callout to make sure the mouse events
           * for updating focus are not interacting during scroll
           */
          _this._onScroll = function () {
              if (!_this._isScrollIdle && _this._scrollIdleTimeoutId !== undefined) {
                  _this._async.clearTimeout(_this._scrollIdleTimeoutId);
                  _this._scrollIdleTimeoutId = undefined;
              }
              else {
                  _this._isScrollIdle = false;
              }
              _this._scrollIdleTimeoutId = _this._async.setTimeout(function () {
                  _this._isScrollIdle = true;
              }, ScrollIdleDelay);
          };
          _this._onRenderOptionContent = function (item) {
              var optionClassNames = ComboBox_classNames_1.getComboBoxOptionClassNames(_this._getCurrentOptionStyles(item));
              return React.createElement("span", { className: optionClassNames.optionText }, item.text);
          };
          /**
           * Handles dismissing (cancelling) the menu
           */
          _this._onDismiss = function () {
              var onMenuDismiss = _this.props.onMenuDismiss;
              if (onMenuDismiss) {
                  onMenuDismiss();
              }
              // In persistMode we need to simulate callout layer mount
              // since that only happens once. We do it on dismiss since
              // it works either way.
              if (_this.props.persistMenu) {
                  _this._onCalloutLayerMounted();
              }
              // close the menu
              _this._setOpenStateAndFocusOnClose(false /* isOpen */, false /* focusInputAfterClose */);
              // reset the selected index
              // to the last value state
              _this._resetSelectedIndex();
          };
          _this._onAfterClearPendingInfo = function () {
              _this._processingClearPendingInfo = false;
          };
          /**
           * Handle keydown on the input
           * @param ev - The keyboard event that was fired
           */
          _this._onInputKeyDown = function (ev) {
              var _a = _this.props, disabled = _a.disabled, allowFreeform = _a.allowFreeform, autoComplete = _a.autoComplete;
              var _b = _this.state, isOpen = _b.isOpen, currentOptions = _b.currentOptions, currentPendingValueValidIndexOnHover = _b.currentPendingValueValidIndexOnHover;
              // Take note if we are processing an alt (option) or meta (command) keydown.
              // See comment in _onInputKeyUp for reasoning.
              _this._lastKeyDownWasAltOrMeta = _this._isAltOrMeta(ev);
              if (disabled) {
                  _this._handleInputWhenDisabled(ev);
                  return;
              }
              var index = _this._getPendingSelectedIndex(false /* includeCurrentPendingValue */);
              switch (ev.which) {
                  case Utilities_1.KeyCodes.enter:
                      if (_this._autofill.current && _this._autofill.current.inputElement) {
                          _this._autofill.current.inputElement.select();
                      }
                      _this._submitPendingValue(ev);
                      if (_this.props.multiSelect && isOpen) {
                          _this.setState({
                              currentPendingValueValidIndex: index
                          });
                      }
                      else {
                          // On enter submit the pending value
                          if (isOpen ||
                              ((!allowFreeform ||
                                  _this.state.currentPendingValue === undefined ||
                                  _this.state.currentPendingValue === null ||
                                  _this.state.currentPendingValue.length <= 0) &&
                                  _this.state.currentPendingValueValidIndex < 0)) {
                              // if we are open or
                              // if we are not allowing freeform or
                              // our we have no pending value
                              // and no valid pending index
                              // flip the open state
                              _this.setState({
                                  isOpen: !isOpen
                              });
                          }
                      }
                      break;
                  case Utilities_1.KeyCodes.tab:
                      // On enter submit the pending value
                      if (!_this.props.multiSelect) {
                          _this._submitPendingValue(ev);
                      }
                      // If we are not allowing freeform
                      // or the comboBox is open, flip the open state
                      if (isOpen) {
                          _this._setOpenStateAndFocusOnClose(!isOpen, false /* focusInputAfterClose */);
                      }
                      // Allow TAB to propigate
                      return;
                  case Utilities_1.KeyCodes.escape:
                      // reset the selected index
                      _this._resetSelectedIndex();
                      // Close the menu if opened
                      if (isOpen) {
                          _this.setState({
                              isOpen: false
                          });
                      }
                      else {
                          return;
                      }
                      break;
                  case Utilities_1.KeyCodes.up:
                      // if we are in clearAll state (e.g. the user as hovering
                      // and has since mousedOut of the menu items),
                      // go to the last index
                      if (currentPendingValueValidIndexOnHover === HoverStatus.clearAll) {
                          index = _this.state.currentOptions.length;
                      }
                      if (ev.altKey || ev.metaKey) {
                          // Close the menu if it is open and break so
                          // that the event get stopPropagation and prevent default.
                          // Otherwise, we need to let the event continue to propagate
                          if (isOpen) {
                              _this._setOpenStateAndFocusOnClose(!isOpen, true /* focusInputAfterClose */);
                              break;
                          }
                          return;
                      }
                      // Go to the previous option
                      _this._setPendingInfoFromIndexAndDirection(index, SearchDirection.backward);
                      break;
                  case Utilities_1.KeyCodes.down:
                      // Expand the comboBox on ALT + DownArrow
                      if (ev.altKey || ev.metaKey) {
                          _this._setOpenStateAndFocusOnClose(true /* isOpen */, true /* focusInputAfterClose */);
                      }
                      else {
                          // if we are in clearAll state (e.g. the user as hovering
                          // and has since mousedOut of the menu items),
                          // go to the first index
                          if (currentPendingValueValidIndexOnHover === HoverStatus.clearAll) {
                              index = -1;
                          }
                          // Got to the next option
                          _this._setPendingInfoFromIndexAndDirection(index, SearchDirection.forward);
                      }
                      break;
                  case Utilities_1.KeyCodes.home:
                  case Utilities_1.KeyCodes.end:
                      if (allowFreeform) {
                          return;
                      }
                      // Set the initial values to respond to HOME
                      // which goes to the first selectable option
                      index = -1;
                      var directionToSearch = SearchDirection.forward;
                      // If end, update the values to respond to END
                      // which goes to the last selectable option
                      if (ev.which === Utilities_1.KeyCodes.end) {
                          index = currentOptions.length;
                          directionToSearch = SearchDirection.backward;
                      }
                      _this._setPendingInfoFromIndexAndDirection(index, directionToSearch);
                      break;
                  case Utilities_1.KeyCodes.space:
                      // event handled in _onComboBoxKeyUp
                      if (!allowFreeform && autoComplete === 'off') {
                          break;
                      }
                  default:
                      // are we processing a function key? if so bail out
                      if (ev.which >= 112 /* F1 */ && ev.which <= 123 /* F12 */) {
                          return;
                      }
                      // If we get here and we got either and ALT key
                      // or meta key, let the event propagate
                      if (ev.keyCode === Utilities_1.KeyCodes.alt || ev.key === 'Meta' /* && isOpen */) {
                          return;
                      }
                      // If we are not allowing freeform and
                      // allowing autoComplete, handle the input here
                      // since we have marked the input as readonly
                      if (!allowFreeform && autoComplete === 'on') {
                          _this._onInputChange(String.fromCharCode(ev.which));
                          break;
                      }
                      // allow the key to propagate by default
                      return;
              }
              ev.stopPropagation();
              ev.preventDefault();
          };
          /**
           * Handle keyup on the input
           * @param ev - the keyboard event that was fired
           */
          _this._onInputKeyUp = function (ev) {
              var _a = _this.props, disabled = _a.disabled, allowFreeform = _a.allowFreeform, autoComplete = _a.autoComplete;
              var isOpen = _this.state.isOpen;
              // We close the menu on key up only if ALL of the following are true:
              // - Most recent key down was alt or meta (command)
              // - The alt/meta key down was NOT followed by some other key (such as down/up arrow to
              //   expand/collapse the menu)
              // - We're not on a Mac (or iOS)
              // This is because on Windows, pressing alt moves focus to the application menu bar or similar,
              // closing any open context menus. There is not a similar behavior on Macs.
              var keyPressIsAltOrMetaAlone = _this._lastKeyDownWasAltOrMeta && _this._isAltOrMeta(ev);
              _this._lastKeyDownWasAltOrMeta = false;
              var shouldHandleKey = keyPressIsAltOrMetaAlone && !(Utilities_1.isMac() || Utilities_1.isIOS());
              if (disabled) {
                  _this._handleInputWhenDisabled(ev);
                  return;
              }
              switch (ev.which) {
                  case Utilities_1.KeyCodes.space:
                      // If we are not allowing freeform and are not autoComplete
                      // make space expand/collapse the comboBox
                      // and allow the event to propagate
                      if (!allowFreeform && autoComplete === 'off') {
                          _this._setOpenStateAndFocusOnClose(!isOpen, !!isOpen);
                      }
                      return;
                  default:
                      if (shouldHandleKey && isOpen) {
                          _this._setOpenStateAndFocusOnClose(!isOpen, true /* focusInputAfterClose */);
                      }
                      return;
              }
          };
          _this._onOptionMouseLeave = function () {
              if (_this._shouldIgnoreMouseEvent()) {
                  return;
              }
              // Ignore the event in persistMenu mode if the callout has
              // closed. This is to avoid clearing the visuals on item click.
              if (_this.props.persistMenu && !_this.state.isOpen) {
                  return;
              }
              _this.setState({
                  currentPendingValueValidIndexOnHover: HoverStatus.clearAll
              });
          };
          /**
           * Click handler for the button of the comboBox
           * and the input when not allowing freeform. This
           * toggles the expand/collapse state of the comboBox (if enbled)
           */
          _this._onComboBoxClick = function () {
              var disabled = _this.props.disabled;
              var isOpen = _this.state.isOpen;
              if (!disabled) {
                  _this._setOpenStateAndFocusOnClose(!isOpen, false /* focusInputAfterClose */);
                  _this.setState({ focused: true });
              }
          };
          /**
           * Click handler for the autofill.
           */
          _this._onAutofillClick = function () {
              var _a = _this.props, disabled = _a.disabled, allowFreeform = _a.allowFreeform;
              if (allowFreeform && !disabled) {
                  _this.focus(_this.state.isOpen || _this._processingTouch);
              }
              else {
                  _this._onComboBoxClick();
              }
          };
          _this._onTouchStart = function () {
              if (_this._comboBoxWrapper.current && !('onpointerdown' in _this._comboBoxWrapper)) {
                  _this._handleTouchAndPointerEvent();
              }
          };
          _this._onPointerDown = function (ev) {
              if (ev.pointerType === 'touch') {
                  _this._handleTouchAndPointerEvent();
                  ev.preventDefault();
                  ev.stopImmediatePropagation();
              }
          };
          _this._warnMutuallyExclusive({
              defaultSelectedKey: 'selectedKey',
              text: 'defaultSelectedKey',
              selectedKey: 'value',
              dropdownWidth: 'useComboBoxAsMenuWidth'
          });
          _this._id = props.id || Utilities_1.getId('ComboBox');
          var selectedKeys = _this._buildDefaultSelectedKeys(props.defaultSelectedKey, props.selectedKey);
          _this._isScrollIdle = true;
          _this._processingTouch = false;
          _this._gotMouseMove = false;
          _this._processingClearPendingInfo = false;
          var initialSelectedIndices = _this._getSelectedIndices(props.options, selectedKeys);
          _this.state = {
              isOpen: false,
              selectedIndices: initialSelectedIndices,
              focused: false,
              suggestedDisplayValue: undefined,
              currentOptions: _this.props.options,
              currentPendingValueValidIndex: -1,
              currentPendingValue: undefined,
              currentPendingValueValidIndexOnHover: HoverStatus.default
          };
          return _this;
      }
      Object.defineProperty(ComboBox.prototype, "selectedOptions", {
          /**
           * All selected options
           */
          get: function () {
              var _a = this.state, currentOptions = _a.currentOptions, selectedIndices = _a.selectedIndices;
              return index_2.getAllSelectedOptions(currentOptions, selectedIndices);
          },
          enumerable: true,
          configurable: true
      });
      ComboBox.prototype.componentDidMount = function () {
          if (this._comboBoxWrapper.current) {
              // hook up resolving the options if needed on focus
              this._events.on(this._comboBoxWrapper.current, 'focus', this._onResolveOptions, true);
              if ('onpointerdown' in this._comboBoxWrapper.current) {
                  // For ComboBoxes, touching anywhere in the combo box should drop the dropdown, including the input element.
                  // This gives more hit target space for touch environments. We're setting the onpointerdown here, because React
                  // does not support Pointer events yet.
                  this._events.on(this._comboBoxWrapper.current, 'pointerdown', this._onPointerDown, true);
              }
          }
      };
      // tslint:disable-next-line function-name
      ComboBox.prototype.UNSAFE_componentWillReceiveProps = function (newProps) {
          // Update the selectedIndex and currentOptions state if
          // the selectedKey, value, or options have changed
          if (newProps.selectedKey !== this.props.selectedKey || newProps.text !== this.props.text || newProps.options !== this.props.options) {
              var selectedKeys = this._buildSelectedKeys(newProps.selectedKey);
              var indices = this._getSelectedIndices(newProps.options, selectedKeys);
              this.setState({
                  selectedIndices: indices,
                  currentOptions: newProps.options
              });
              if (newProps.selectedKey === null) {
                  this.setState({
                      suggestedDisplayValue: undefined
                  });
              }
          }
      };
      ComboBox.prototype.componentDidUpdate = function (prevProps, prevState) {
          var _this = this;
          var _a = this.props, allowFreeform = _a.allowFreeform, text = _a.text, onMenuOpen = _a.onMenuOpen, onMenuDismissed = _a.onMenuDismissed;
          var _b = this.state, isOpen = _b.isOpen, focused = _b.focused, selectedIndices = _b.selectedIndices, currentPendingValueValidIndex = _b.currentPendingValueValidIndex;
          // If we are newly open or are open and the pending valid index changed,
          // make sure the currently selected/pending option is scrolled into view
          if (isOpen && (!prevState.isOpen || prevState.currentPendingValueValidIndex !== currentPendingValueValidIndex)) {
              // Need this timeout so that the selectedElement ref is correctly updated
              this._async.setTimeout(function () { return _this._scrollIntoView(); }, 0);
          }
          // if an action is taken that put focus in the ComboBox
          // and If we are open or we are just closed, shouldFocusAfterClose is set,
          // but we are not the activeElement set focus on the input
          if (focused &&
              (isOpen ||
                  (prevState.isOpen &&
                      !isOpen &&
                      this._focusInputAfterClose &&
                      this._autofill.current &&
                      document.activeElement !== this._autofill.current.inputElement))) {
              this.focus(undefined /*shouldOpenOnFocus*/, true /*useFocusAsync*/);
          }
          // If we should focusAfterClose AND
          //   just opened/closed the menu OR
          //   are focused AND
          //     updated the selectedIndex with the menu closed OR
          //     are not allowing freeform OR
          //     the value changed
          // we need to set selection
          if (this._focusInputAfterClose &&
              ((prevState.isOpen && !isOpen) ||
                  (focused &&
                      ((!isOpen &&
                          !this.props.multiSelect &&
                          prevState.selectedIndices &&
                          selectedIndices &&
                          prevState.selectedIndices[0] !== selectedIndices[0]) ||
                          !allowFreeform ||
                          text !== prevProps.text)))) {
              this._select();
          }
          this._notifyPendingValueChanged(prevState);
          if (isOpen && !prevState.isOpen && onMenuOpen) {
              onMenuOpen();
          }
          if (!isOpen && prevState.isOpen && onMenuDismissed) {
              onMenuDismissed();
          }
      };
      ComboBox.prototype.componentWillUnmount = function () {
          _super.prototype.componentWillUnmount.call(this);
          // remove the eventHanlder that was added in componentDidMount
          this._events.off(this._comboBoxWrapper.current);
      };
      // Primary Render
      ComboBox.prototype.render = function () {
          var _this = this;
          var id = this._id;
          var errorMessageId = id + '-error';
          var _a = this.props, className = _a.className, label = _a.label, disabled = _a.disabled, ariaLabel = _a.ariaLabel, ariaDescribedBy = _a.ariaDescribedBy, required = _a.required, errorMessage = _a.errorMessage, _b = _a.onRenderContainer, onRenderContainer = _b === void 0 ? this._onRenderContainer : _b, _c = _a.onRenderList, onRenderList = _c === void 0 ? this._onRenderList : _c, _d = _a.onRenderItem, onRenderItem = _d === void 0 ? this._onRenderItem : _d, _e = _a.onRenderOption, onRenderOption = _e === void 0 ? this._onRenderOptionContent : _e, allowFreeform = _a.allowFreeform, buttonIconProps = _a.buttonIconProps, _f = _a.isButtonAriaHidden, isButtonAriaHidden = _f === void 0 ? true : _f, customStyles = _a.styles, theme = _a.theme, title = _a.title, keytipProps = _a.keytipProps, placeholder = _a.placeholder, tabIndex = _a.tabIndex, autofill = _a.autofill, persistMenu = _a.persistMenu, iconButtonProps = _a.iconButtonProps;
          var _g = this.state, isOpen = _g.isOpen, focused = _g.focused, suggestedDisplayValue = _g.suggestedDisplayValue;
          this._currentVisibleValue = this._getVisibleValue();
          var divProps = Utilities_1.getNativeProps(this.props, Utilities_1.divProperties, ['onChange', 'value']);
          var hasErrorMessage = errorMessage && errorMessage.length > 0 ? true : false;
          this._classNames = this.props.getClassNames
              ? this.props.getClassNames(theme, !!isOpen, !!disabled, !!required, !!focused, !!allowFreeform, !!hasErrorMessage, className)
              : ComboBox_classNames_1.getClassNames(ComboBox_styles_1.getStyles(theme, customStyles), className, !!isOpen, !!disabled, !!required, !!focused, !!allowFreeform, !!hasErrorMessage);
          return (React.createElement("div", tslib_1.__assign({}, divProps, { ref: this._root, className: this._classNames.container }),
              label && (React.createElement(Label_1.Label, { id: id + '-label', disabled: disabled, required: required, htmlFor: id + '-input', className: this._classNames.label }, label)),
              React.createElement(KeytipData_1.KeytipData, { keytipProps: keytipProps, disabled: disabled }, function (keytipAttributes) { return (React.createElement("div", { "data-ktp-target": keytipAttributes['data-ktp-target'], ref: _this._comboBoxWrapper, id: id + 'wrapper', className: _this._classNames.root },
                  React.createElement(index_1.Autofill, tslib_1.__assign({ "data-ktp-execute-target": keytipAttributes['data-ktp-execute-target'], "data-is-interactable": !disabled, componentRef: _this._autofill, id: id + '-input', className: _this._classNames.input, type: "text", onFocus: _this._select, onBlur: _this._onBlur, onKeyDown: _this._onInputKeyDown, onKeyUp: _this._onInputKeyUp, onClick: _this._onAutofillClick, onTouchStart: _this._onTouchStart, onInputValueChange: _this._onInputChange, "aria-expanded": isOpen, "aria-autocomplete": _this._getAriaAutoCompleteValue(), role: "combobox", readOnly: disabled || !allowFreeform, "aria-labelledby": label && id + '-label', "aria-label": ariaLabel && !label ? ariaLabel : undefined, "aria-describedby": errorMessage !== undefined
                          ? Utilities_1.mergeAriaAttributeValues(ariaDescribedBy, keytipAttributes['aria-describedby'], errorMessageId)
                          : Utilities_1.mergeAriaAttributeValues(ariaDescribedBy, keytipAttributes['aria-describedby']), "aria-activedescendant": _this._getAriaActiveDescentValue(), "aria-required": required, "aria-disabled": disabled, "aria-owns": isOpen ? id + '-list' : undefined, spellCheck: false, defaultVisibleValue: _this._currentVisibleValue, suggestedDisplayValue: suggestedDisplayValue, updateValueInWillReceiveProps: _this._onUpdateValueInAutofillWillReceiveProps, shouldSelectFullInputValueInComponentDidUpdate: _this._onShouldSelectFullInputValueInAutofillComponentDidUpdate, title: title, preventValueSelection: !focused, placeholder: placeholder, tabIndex: tabIndex }, autofill)),
                  React.createElement(Button_1.IconButton, tslib_1.__assign({ className: 'ms-ComboBox-CaretDown-button', styles: _this._getCaretButtonStyles(), role: "presentation", "aria-hidden": isButtonAriaHidden, "data-is-focusable": false, tabIndex: -1, onClick: _this._onComboBoxClick, onBlur: _this._onBlur, iconProps: buttonIconProps, disabled: disabled, checked: isOpen }, iconButtonProps)))); }),
              (persistMenu || isOpen) &&
                  onRenderContainer(tslib_1.__assign({}, this.props, { onRenderList: onRenderList,
                      onRenderItem: onRenderItem,
                      onRenderOption: onRenderOption, options: this.state.currentOptions.map(function (item, index) { return (tslib_1.__assign({}, item, { index: index })); }), onDismiss: this._onDismiss }), this._onRenderContainer),
              React.createElement("div", { role: "region", "aria-live": "polite", "aria-atomic": "true", id: errorMessageId, className: this._classNames.errorMessage }, errorMessage !== undefined ? errorMessage : '')));
      };
      /**
       * Is the index within the bounds of the array?
       * @param options - options to check if the index is valid for
       * @param index - the index to check
       * @returns - true if the index is valid for the given options, false otherwise
       */
      ComboBox.prototype._indexWithinBounds = function (options, index) {
          if (!options) {
              return false;
          }
          return index >= 0 && index < options.length;
      };
      /**
       * Process the new input's new value when the comboBox
       * allows freeform entry
       * @param updatedValue - the input's newly changed value
       */
      ComboBox.prototype._processInputChangeWithFreeform = function (updatedValue) {
          var _this = this;
          var currentOptions = this.state.currentOptions;
          updatedValue = this._removeZeroWidthSpaces(updatedValue);
          var newCurrentPendingValueValidIndex = -1;
          // if the new value is empty, see if we have an exact match
          // and then set the pending info
          if (updatedValue === '') {
              var items = currentOptions
                  .map(function (item, index) {
                  return tslib_1.__assign({}, item, { index: index });
              })
                  .filter(function (option) { return option.itemType !== index_2.SelectableOptionMenuItemType.Header && option.itemType !== index_2.SelectableOptionMenuItemType.Divider; })
                  .filter(function (option) { return _this._getPreviewText(option) === updatedValue; });
              // if we found a match remember the index
              if (items.length === 1) {
                  newCurrentPendingValueValidIndex = items[0].index;
              }
              this._setPendingInfo(updatedValue, newCurrentPendingValueValidIndex, updatedValue);
              return;
          }
          // Remember the original value and then,
          // make the value lowercase for comparison
          var originalUpdatedValue = updatedValue;
          updatedValue = updatedValue.toLocaleLowerCase();
          var newSuggestedDisplayValue = '';
          // If autoComplete is on, attempt to find a match from the available options
          if (this.props.autoComplete === 'on') {
              // If autoComplete is on, attempt to find a match where the text of an option starts with the updated value
              var items = currentOptions
                  .map(function (item, index) {
                  return tslib_1.__assign({}, item, { index: index });
              })
                  .filter(function (option) { return option.itemType !== index_2.SelectableOptionMenuItemType.Header && option.itemType !== index_2.SelectableOptionMenuItemType.Divider; })
                  .filter(function (option) {
                  return _this._getPreviewText(option)
                      .toLocaleLowerCase()
                      .indexOf(updatedValue) === 0;
              });
              if (items.length > 0) {
                  // use ariaLabel as the value when the option is set
                  var text = this._getPreviewText(items[0]);
                  // If the user typed out the complete option text, we don't need any suggested display text anymore
                  newSuggestedDisplayValue = text.toLocaleLowerCase() !== updatedValue ? text : '';
                  // remember the index of the match we found
                  newCurrentPendingValueValidIndex = items[0].index;
              }
          }
          else {
              // If autoComplete is off, attempt to find a match only when the value is exactly equal to the text of an option
              var items = currentOptions
                  .map(function (item, index) {
                  return tslib_1.__assign({}, item, { index: index });
              })
                  .filter(function (option) { return option.itemType !== index_2.SelectableOptionMenuItemType.Header && option.itemType !== index_2.SelectableOptionMenuItemType.Divider; })
                  .filter(function (option) { return _this._getPreviewText(option).toLocaleLowerCase() === updatedValue; });
              // if we found a match remember the index
              if (items.length === 1) {
                  newCurrentPendingValueValidIndex = items[0].index;
              }
          }
          // Set the updated state
          this._setPendingInfo(originalUpdatedValue, newCurrentPendingValueValidIndex, newSuggestedDisplayValue);
      };
      /**
       * Process the new input's new value when the comboBox
       * does not allow freeform entry
       * @param updatedValue - the input's newly changed value
       */
      ComboBox.prototype._processInputChangeWithoutFreeform = function (updatedValue) {
          var _this = this;
          var _a = this.state, currentPendingValue = _a.currentPendingValue, currentPendingValueValidIndex = _a.currentPendingValueValidIndex, currentOptions = _a.currentOptions;
          updatedValue = this._removeZeroWidthSpaces(updatedValue);
          if (this.props.autoComplete === 'on') {
              // If autoComplete is on while allow freeform is off,
              // we will remember the keypresses and build up a string to attempt to match
              // as long as characters are typed within a the timeout span of each other,
              // otherwise we will clear the string and start building a new one on the next keypress.
              // Also, only do this processing if we have a non-empty value
              if (updatedValue !== '') {
                  // If we have a pending autocomplete clearing task,
                  // we know that the user is typing with keypresses happening
                  // within the timeout of each other so remove the clearing task
                  // and continue building the pending value with the udpated value
                  if (this._lastReadOnlyAutoCompleteChangeTimeoutId !== undefined) {
                      this._async.clearTimeout(this._lastReadOnlyAutoCompleteChangeTimeoutId);
                      this._lastReadOnlyAutoCompleteChangeTimeoutId = undefined;
                      updatedValue = this._normalizeToString(currentPendingValue) + updatedValue;
                  }
                  var originalUpdatedValue = updatedValue;
                  updatedValue = updatedValue.toLocaleLowerCase();
                  // If autoComplete is on, attempt to find a match where the text of an option starts with the updated value
                  var items = currentOptions
                      .map(function (item, i) {
                      return tslib_1.__assign({}, item, { index: i });
                  })
                      .filter(function (option) { return option.itemType !== index_2.SelectableOptionMenuItemType.Header && option.itemType !== index_2.SelectableOptionMenuItemType.Divider; })
                      .filter(function (option) { return option.text.toLocaleLowerCase().indexOf(updatedValue) === 0; });
                  // If we found a match, udpdate the state
                  if (items.length > 0) {
                      this._setPendingInfo(originalUpdatedValue, items[0].index, this._getPreviewText(items[0]));
                  }
                  // Schedule a timeout to clear the pending value after the timeout span
                  this._lastReadOnlyAutoCompleteChangeTimeoutId = this._async.setTimeout(function () {
                      _this._lastReadOnlyAutoCompleteChangeTimeoutId = undefined;
                  }, ReadOnlyPendingAutoCompleteTimeout);
                  return;
              }
          }
          // If we get here, either autoComplete is on or we did not find a match with autoComplete on.
          // Remember we are not allowing freeform, so at this point, if we have a pending valid value index
          // use that; otherwise use the selectedIndex
          var index = currentPendingValueValidIndex >= 0 ? currentPendingValueValidIndex : this._getFirstSelectedIndex();
          // Since we are not allowing freeform, we need to
          // set both the pending and suggested values/index
          // to allow us to select all content in the input to
          // give the illusion that we are readonly (e.g. freeform off)
          this._setPendingInfoFromIndex(index);
      };
      ComboBox.prototype._getFirstSelectedIndex = function () {
          return this.state.selectedIndices && this.state.selectedIndices.length > 0 ? this.state.selectedIndices[0] : -1;
      };
      /**
       * Walk along the options starting at the index, stepping by the delta (positive or negative)
       * looking for the next valid selectable index (e.g. skipping headings and dividers)
       * @param index - the index to get the next selectable index from
       * @param delta - optional delta to step by when finding the next index, defaults to 0
       * @returns - the next valid selectable index. If the new index is outside of the bounds,
       * it will snap to the edge of the options array. If delta == 0 and the given index is not selectable
       */
      ComboBox.prototype._getNextSelectableIndex = function (index, searchDirection) {
          var currentOptions = this.state.currentOptions;
          var newIndex = index + searchDirection;
          newIndex = Math.max(0, Math.min(currentOptions.length - 1, newIndex));
          if (!this._indexWithinBounds(currentOptions, newIndex)) {
              return -1;
          }
          var option = currentOptions[newIndex];
          if (option.itemType === index_2.SelectableOptionMenuItemType.Header ||
              option.itemType === index_2.SelectableOptionMenuItemType.Divider ||
              option.hidden === true) {
              // Should we continue looking for an index to select?
              if (searchDirection !== SearchDirection.none &&
                  ((newIndex > 0 && searchDirection < SearchDirection.none) ||
                      (newIndex >= 0 && newIndex < currentOptions.length && searchDirection > SearchDirection.none))) {
                  newIndex = this._getNextSelectableIndex(newIndex, searchDirection);
              }
              else {
                  // If we cannot perform a useful search just return the index we were given
                  return index;
              }
          }
          // We have the next valid selectable index, return it
          return newIndex;
      };
      /**
       * Set the selected index. Note, this is
       * the "real" selected index, not the pending selected index
       * @param index - the index to set (or the index to set from if a search direction is provided)
       * @param searchDirection - the direction to search along the options from the given index
       */
      ComboBox.prototype._setSelectedIndex = function (index, submitPendingValueEvent, searchDirection) {
          var _this = this;
          if (searchDirection === void 0) { searchDirection = SearchDirection.none; }
          var _a = this.props, onChange = _a.onChange, onPendingValueChanged = _a.onPendingValueChanged;
          var currentOptions = this.state.currentOptions;
          var initialIndices = this.state.selectedIndices;
          // Clone selectedIndices so we don't mutate state
          var selectedIndices = initialIndices ? initialIndices.slice() : [];
          // Find the next selectable index, if searchDirection is none
          // we will get our starting index back
          index = this._getNextSelectableIndex(index, searchDirection);
          if (!this._indexWithinBounds(currentOptions, index)) {
              return;
          }
          // Are we at a new index? If so, update the state, otherwise
          // there is nothing to do
          if (this.props.multiSelect || selectedIndices.length < 1 || (selectedIndices.length === 1 && selectedIndices[0] !== index)) {
              var option_1 = tslib_1.__assign({}, currentOptions[index]);
              if (!option_1) {
                  return;
              }
              if (this.props.multiSelect) {
                  // Setting the initial state of option.selected in Multi-select combobox by checking the
                  // selectedIndices array and overriding the undefined issue
                  option_1.selected = option_1.selected !== undefined ? !option_1.selected : selectedIndices.indexOf(index) < 0;
                  if (option_1.selected && selectedIndices.indexOf(index) < 0) {
                      selectedIndices.push(index);
                  }
                  else if (!option_1.selected && selectedIndices.indexOf(index) >= 0) {
                      selectedIndices = selectedIndices.filter(function (value) { return value !== index; });
                  }
              }
              else {
                  selectedIndices[0] = index;
              }
              submitPendingValueEvent.persist();
              // Only setstate if combobox is uncontrolled.
              if (this.props.selectedKey || this.props.selectedKey === null) {
                  // If ComboBox value is changed, revert preview first
                  if (this._hasPendingValue && onPendingValueChanged) {
                      onPendingValueChanged();
                      this._hasPendingValue = false;
                  }
                  if (onChange) {
                      onChange(submitPendingValueEvent, option_1, index, undefined);
                  }
              }
              else {
                  // Update current options
                  var changedOptions = currentOptions.slice();
                  changedOptions[index] = option_1;
                  // Call onChange after state is updated
                  this.setState({
                      selectedIndices: selectedIndices,
                      currentOptions: changedOptions
                  }, function () {
                      // If ComboBox value is changed, revert preview first
                      if (_this._hasPendingValue && onPendingValueChanged) {
                          onPendingValueChanged();
                          _this._hasPendingValue = false;
                      }
                      if (onChange) {
                          onChange(submitPendingValueEvent, option_1, index, undefined);
                      }
                  });
              }
          }
          // clear all of the pending info
          this._clearPendingInfo();
      };
      /**
       * Submit a pending value if there is one
       */
      ComboBox.prototype._submitPendingValue = function (submitPendingValueEvent) {
          var _a = this.props, onChange = _a.onChange, allowFreeform = _a.allowFreeform, autoComplete = _a.autoComplete;
          var _b = this.state, currentPendingValue = _b.currentPendingValue, currentPendingValueValidIndex = _b.currentPendingValueValidIndex, currentOptions = _b.currentOptions, currentPendingValueValidIndexOnHover = _b.currentPendingValueValidIndexOnHover;
          var selectedIndices = this.state.selectedIndices;
          // Do not submit any pending value if we
          // have already initiated clearing the pending info
          if (this._processingClearPendingInfo) {
              return;
          }
          // If we allow freeform we need to handle that
          if (allowFreeform) {
              // if currentPendingValue is null or undefined the user did not submit anything
              // (not even empty because we would have stored that as the pending value)
              if (currentPendingValue === null || currentPendingValue === undefined) {
                  // if a user did not type anything they may just hovered over an item
                  if (currentPendingValueValidIndexOnHover >= 0) {
                      this._setSelectedIndex(currentPendingValueValidIndexOnHover, submitPendingValueEvent);
                      this._clearPendingInfo();
                  }
                  return;
              }
              // Check to see if the user typed an exact match
              if (this._indexWithinBounds(currentOptions, currentPendingValueValidIndex)) {
                  var pendingOptionText = this._getPreviewText(currentOptions[currentPendingValueValidIndex]).toLocaleLowerCase();
                  // By exact match, that means: our pending value is the same as the the pending option text OR
                  // the pending option starts with the pending value and we have an "autoComplete" selection
                  // where the total length is equal to pending option length OR
                  // the live value in the underlying input matches the pending option; update the state
                  if (currentPendingValue.toLocaleLowerCase() === pendingOptionText ||
                      ((autoComplete &&
                          pendingOptionText.indexOf(currentPendingValue.toLocaleLowerCase()) === 0 &&
                          (this._autofill.current &&
                              this._autofill.current.isValueSelected &&
                              currentPendingValue.length + (this._autofill.current.selectionEnd - this._autofill.current.selectionStart) ===
                                  pendingOptionText.length)) ||
                          (this._autofill.current &&
                              this._autofill.current.inputElement &&
                              this._autofill.current.inputElement.value.toLocaleLowerCase() === pendingOptionText))) {
                      this._setSelectedIndex(currentPendingValueValidIndex, submitPendingValueEvent);
                      this._clearPendingInfo();
                      return;
                  }
              }
              if (onChange) {
                  if (onChange) {
                      // trigger onChange to clear value
                      onChange(submitPendingValueEvent, undefined, undefined, currentPendingValue);
                  }
              }
              else {
                  // If we are not controlled, create a new selected option
                  var newOption = {
                      key: currentPendingValue || Utilities_1.getId(),
                      text: this._normalizeToString(currentPendingValue)
                  };
                  // If it's multiselect, set selected state to true
                  if (this.props.multiSelect) {
                      newOption.selected = true;
                  }
                  var newOptions = currentOptions.concat([newOption]);
                  if (selectedIndices) {
                      if (!this.props.multiSelect) {
                          selectedIndices = [];
                      }
                      selectedIndices.push(newOptions.length - 1);
                  }
                  this.setState({
                      currentOptions: newOptions,
                      selectedIndices: selectedIndices
                  });
              }
          }
          else if (currentPendingValueValidIndex >= 0) {
              // Since we are not allowing freeform, we must have a matching
              // to be able to update state
              this._setSelectedIndex(currentPendingValueValidIndex, submitPendingValueEvent);
          }
          else if (currentPendingValueValidIndexOnHover >= 0) {
              // If all else failed and we were hovering over an item, select it
              this._setSelectedIndex(currentPendingValueValidIndexOnHover, submitPendingValueEvent);
          }
          // Finally, clear the pending info
          this._clearPendingInfo();
      };
      ComboBox.prototype._onCalloutLayerMounted = function () {
          // In persistMenu mode _onLayerMounted is only called once for the lifetime
          // of the component. Any functionality required for callout "on mount" can
          // go here so that we can also call it again during callout dismissal to reset
          // object state.
          this._gotMouseMove = false;
      };
      // Render separator
      ComboBox.prototype._renderSeparator = function (item) {
          var index = item.index, key = item.key;
          if (index && index > 0) {
              return React.createElement("div", { role: "separator", key: key, className: this._classNames.divider });
          }
          return null;
      };
      ComboBox.prototype._renderHeader = function (item) {
          var _a = this.props.onRenderOption, onRenderOption = _a === void 0 ? this._onRenderOptionContent : _a;
          return (React.createElement("div", { key: item.key, className: this._classNames.header }, onRenderOption(item, this._onRenderOptionContent)));
      };
      /**
       * If we are coming from a mouseOut:
       * there is no visible selected option.
       *
       * Else if We are hovering over an item:
       * that gets the selected look.
       *
       * Else:
       * Use the current valid pending index if it exists OR
       * we do not have a valid index and we currently have a pending input value,
       * otherwise use the selected index
       * */
      ComboBox.prototype._isOptionSelected = function (index) {
          var currentPendingValueValidIndexOnHover = this.state.currentPendingValueValidIndexOnHover;
          // If the hover state is set to clearAll, don't show a selected index.
          // Note, this happens when the user moused out of the menu items
          if (currentPendingValueValidIndexOnHover === HoverStatus.clearAll) {
              return false;
          }
          if (!this.props.multiSelect && this._getPendingSelectedIndex(true /* includePendingValue */) === index) {
              return true;
          }
          var idxOfSelectedIndex = -1;
          if (this.props.multiSelect && index !== undefined && this.state.selectedIndices) {
              idxOfSelectedIndex = this.state.selectedIndices.indexOf(index);
          }
          return idxOfSelectedIndex >= 0;
      };
      /**
       * Gets the pending selected index taking into account hover, valueValidIndex, and selectedIndex
       * @param includeCurrentPendingValue - Should we include the currentPendingValue when
       * finding the index
       */
      ComboBox.prototype._getPendingSelectedIndex = function (includeCurrentPendingValue) {
          var _a = this.state, currentPendingValueValidIndexOnHover = _a.currentPendingValueValidIndexOnHover, currentPendingValueValidIndex = _a.currentPendingValueValidIndex, currentPendingValue = _a.currentPendingValue;
          return currentPendingValueValidIndexOnHover >= 0
              ? currentPendingValueValidIndexOnHover
              : currentPendingValueValidIndex >= 0 ||
                  (includeCurrentPendingValue && (currentPendingValue !== null && currentPendingValue !== undefined))
                  ? currentPendingValueValidIndex
                  : this.props.multiSelect
                      ? 0
                      : this._getFirstSelectedIndex();
      };
      /**
       * Scroll the selected element into view
       */
      ComboBox.prototype._scrollIntoView = function () {
          var _a = this.props, onScrollToItem = _a.onScrollToItem, scrollSelectedToTop = _a.scrollSelectedToTop;
          var _b = this.state, currentPendingValueValidIndex = _b.currentPendingValueValidIndex, currentPendingValue = _b.currentPendingValue;
          if (onScrollToItem) {
              // Use the custom scroll handler
              onScrollToItem(currentPendingValueValidIndex >= 0 || currentPendingValue !== '' ? currentPendingValueValidIndex : this._getFirstSelectedIndex());
          }
          else if (this._selectedElement.current && this._selectedElement.current.offsetParent) {
              // We are using refs, scroll the ref into view
              if (scrollSelectedToTop) {
                  this._selectedElement.current.offsetParent.scrollIntoView(true);
              }
              else {
                  var alignToTop = true;
                  if (this._comboBoxMenu.current && this._comboBoxMenu.current.offsetParent) {
                      var scrollableParentRect = this._comboBoxMenu.current.offsetParent.getBoundingClientRect();
                      var selectedElementRect = this._selectedElement.current.offsetParent.getBoundingClientRect();
                      // If we are completely in view then we do not need to scroll
                      if (scrollableParentRect.top <= selectedElementRect.top &&
                          scrollableParentRect.top + scrollableParentRect.height >= selectedElementRect.top + selectedElementRect.height) {
                          return;
                      }
                      // If we are lower than the scrollable parent viewport then we should align to the bottom
                      if (scrollableParentRect.top + scrollableParentRect.height <= selectedElementRect.top + selectedElementRect.height) {
                          alignToTop = false;
                      }
                  }
                  this._selectedElement.current.offsetParent.scrollIntoView(alignToTop);
              }
          }
      };
      /**
       * Click handler for the menu items
       * to select the item and also close the menu
       * @param index - the index of the item that was clicked
       */
      ComboBox.prototype._onItemClick = function (item) {
          var _this = this;
          var onItemClick = this.props.onItemClick;
          var index = item.index;
          return function (ev) {
              onItemClick && onItemClick(ev, item, index);
              _this._setSelectedIndex(index, ev);
              if (!_this.props.multiSelect) {
                  // only close the callout when it's in single-select mode
                  _this.setState({
                      isOpen: false
                  });
              }
          };
      };
      /**
       * Get the indices of the options that are marked as selected
       * @param options - the comboBox options
       * @param selectedKeys - the known selected keys to find
       * @returns - an array of the indices of the selected options, empty array if nothing is selected
       */
      ComboBox.prototype._getSelectedIndices = function (options, selectedKeys) {
          if (!options || !selectedKeys) {
              return [];
          }
          var selectedIndices = {};
          options.forEach(function (option, index) {
              if (option.selected) {
                  selectedIndices[index] = true;
              }
          });
          var _loop_1 = function (selectedKey) {
              var index = Utilities_1.findIndex(options, function (option) { return option.key === selectedKey; });
              if (index > -1) {
                  selectedIndices[index] = true;
              }
          };
          for (var _i = 0, selectedKeys_1 = selectedKeys; _i < selectedKeys_1.length; _i++) {
              var selectedKey = selectedKeys_1[_i];
              _loop_1(selectedKey);
          }
          return Object.keys(selectedIndices)
              .map(Number)
              .sort();
      };
      /**
       * Reset the selected index by clearing the
       * input (of any pending text), clearing the pending state,
       * and setting the suggested display value to the last
       * selected state text
       */
      ComboBox.prototype._resetSelectedIndex = function () {
          var currentOptions = this.state.currentOptions;
          this._clearPendingInfo();
          var selectedIndex = this._getFirstSelectedIndex();
          if (selectedIndex > 0 && selectedIndex < currentOptions.length) {
              this.setState({
                  suggestedDisplayValue: currentOptions[selectedIndex].text
              });
          }
          else if (this.props.text) {
              // If we had a value initially, restore it
              this.setState({
                  suggestedDisplayValue: this.props.text
              });
          }
      };
      /**
       * Clears the pending info state
       */
      ComboBox.prototype._clearPendingInfo = function () {
          this._processingClearPendingInfo = true;
          this.setState({
              currentPendingValue: undefined,
              currentPendingValueValidIndex: -1,
              suggestedDisplayValue: undefined,
              currentPendingValueValidIndexOnHover: HoverStatus.default
          }, this._onAfterClearPendingInfo);
      };
      /**
       * Set the pending info
       * @param currentPendingValue - new pending value to set
       * @param currentPendingValueValidIndex - new pending value index to set
       * @param suggestedDisplayValue - new suggest display value to set
       */
      ComboBox.prototype._setPendingInfo = function (currentPendingValue, currentPendingValueValidIndex, suggestedDisplayValue) {
          if (currentPendingValueValidIndex === void 0) { currentPendingValueValidIndex = -1; }
          if (this._processingClearPendingInfo) {
              return;
          }
          this.setState({
              currentPendingValue: currentPendingValue && this._removeZeroWidthSpaces(currentPendingValue),
              currentPendingValueValidIndex: currentPendingValueValidIndex,
              suggestedDisplayValue: suggestedDisplayValue,
              currentPendingValueValidIndexOnHover: HoverStatus.default
          });
      };
      /**
       * Set the pending info from the given index
       * @param index - the index to set the pending info from
       */
      ComboBox.prototype._setPendingInfoFromIndex = function (index) {
          var currentOptions = this.state.currentOptions;
          if (index >= 0 && index < currentOptions.length) {
              var option = currentOptions[index];
              this._setPendingInfo(this._getPreviewText(option), index, this._getPreviewText(option));
          }
          else {
              this._clearPendingInfo();
          }
      };
      /**
       * Sets the pending info for the comboBox
       * @param index - the index to search from
       * @param searchDirection - the direction to search
       */
      ComboBox.prototype._setPendingInfoFromIndexAndDirection = function (index, searchDirection) {
          var currentOptions = this.state.currentOptions;
          // update index to allow content to wrap
          if (searchDirection === SearchDirection.forward && index >= currentOptions.length - 1) {
              index = -1;
          }
          else if (searchDirection === SearchDirection.backward && index <= 0) {
              index = currentOptions.length;
          }
          // get the next "valid" index
          var indexUpdate = this._getNextSelectableIndex(index, searchDirection);
          // if the two indicies are equal we didn't move and
          // we should attempt to get  get the first/last "valid" index to use
          // (Note, this takes care of the potential cases where the first/last
          // item is not focusable), otherwise use the updated index
          if (index === indexUpdate) {
              if (searchDirection === SearchDirection.forward) {
                  index = this._getNextSelectableIndex(-1, searchDirection);
              }
              else if (searchDirection === SearchDirection.backward) {
                  index = this._getNextSelectableIndex(currentOptions.length, searchDirection);
              }
          }
          else {
              index = indexUpdate;
          }
          if (this._indexWithinBounds(currentOptions, index)) {
              this._setPendingInfoFromIndex(index);
          }
      };
      ComboBox.prototype._notifyPendingValueChanged = function (prevState) {
          var onPendingValueChanged = this.props.onPendingValueChanged;
          if (!onPendingValueChanged) {
              return;
          }
          var _a = this.state, currentPendingValue = _a.currentPendingValue, currentOptions = _a.currentOptions, currentPendingValueValidIndex = _a.currentPendingValueValidIndex, currentPendingValueValidIndexOnHover = _a.currentPendingValueValidIndexOnHover;
          var newPendingIndex = undefined;
          var newPendingValue = undefined;
          if (currentPendingValueValidIndexOnHover !== prevState.currentPendingValueValidIndexOnHover &&
              this._indexWithinBounds(currentOptions, currentPendingValueValidIndexOnHover)) {
              // Set new pending index if hover index was changed
              newPendingIndex = currentPendingValueValidIndexOnHover;
          }
          else if (currentPendingValueValidIndex !== prevState.currentPendingValueValidIndex &&
              this._indexWithinBounds(currentOptions, currentPendingValueValidIndex)) {
              // Set new pending index if currentPendingValueValidIndex was changed
              newPendingIndex = currentPendingValueValidIndex;
          }
          else if (currentPendingValue !== prevState.currentPendingValue) {
              // Set pendingValue in the case it was changed and no index was changed
              newPendingValue = currentPendingValue;
          }
          // Notify when there is a new pending index/value. Also, if there is a pending value, it needs to send undefined.
          if (newPendingIndex !== undefined || newPendingValue !== undefined || this._hasPendingValue) {
              onPendingValueChanged(newPendingIndex !== undefined ? currentOptions[newPendingIndex] : undefined, newPendingIndex, newPendingValue);
              this._hasPendingValue = newPendingIndex !== undefined || newPendingValue !== undefined;
          }
      };
      /**
       * Sets the isOpen state and updates focusInputAfterClose
       */
      ComboBox.prototype._setOpenStateAndFocusOnClose = function (isOpen, focusInputAfterClose) {
          this._focusInputAfterClose = focusInputAfterClose;
          this.setState({
              isOpen: isOpen
          });
      };
      /**
       * Returns true if the key for the event is alt (Mac option) or meta (Mac command).
       */
      ComboBox.prototype._isAltOrMeta = function (ev) {
          return ev.which === Utilities_1.KeyCodes.alt || ev.key === 'Meta';
      };
      ComboBox.prototype._onOptionMouseEnter = function (index) {
          if (this._shouldIgnoreMouseEvent()) {
              return;
          }
          this.setState({
              currentPendingValueValidIndexOnHover: index
          });
      };
      ComboBox.prototype._onOptionMouseMove = function (index) {
          this._gotMouseMove = true;
          if (!this._isScrollIdle || this.state.currentPendingValueValidIndexOnHover === index) {
              return;
          }
          this.setState({
              currentPendingValueValidIndexOnHover: index
          });
      };
      ComboBox.prototype._shouldIgnoreMouseEvent = function () {
          return !this._isScrollIdle || !this._gotMouseMove;
      };
      /**
       * Handle dismissing the menu and
       * eating the required key event when disabled
       * @param ev - the keyboard event that was fired
       */
      ComboBox.prototype._handleInputWhenDisabled = function (ev) {
          // If we are disabled, close the menu (if needed)
          // and eat all keystokes other than TAB or ESC
          if (this.props.disabled) {
              if (this.state.isOpen) {
                  this.setState({ isOpen: false });
              }
              // When disabled stop propagation and prevent default
              // of the event unless we have a tab, escape, or function key
              if (ev !== null &&
                  ev.which !== Utilities_1.KeyCodes.tab &&
                  ev.which !== Utilities_1.KeyCodes.escape &&
                  (ev.which < 112 /* F1 */ || ev.which > 123) /* F12 */) {
                  ev.stopPropagation();
                  ev.preventDefault();
              }
          }
      };
      ComboBox.prototype._handleTouchAndPointerEvent = function () {
          var _this = this;
          // If we already have an existing timeeout from a previous touch and pointer event
          // cancel that timeout so we can set a nwe one.
          if (this._lastTouchTimeoutId !== undefined) {
              this._async.clearTimeout(this._lastTouchTimeoutId);
              this._lastTouchTimeoutId = undefined;
          }
          this._processingTouch = true;
          this._lastTouchTimeoutId = this._async.setTimeout(function () {
              _this._processingTouch = false;
              _this._lastTouchTimeoutId = undefined;
          }, TouchIdleDelay);
      };
      /**
       * Get the styles for the current option.
       * @param item - Item props for the current option
       */
      ComboBox.prototype._getCaretButtonStyles = function () {
          var customCaretDownButtonStyles = this.props.caretDownButtonStyles;
          return ComboBox_styles_1.getCaretDownButtonStyles(this.props.theme, customCaretDownButtonStyles);
      };
      /**
       * Get the styles for the current option.
       * @param item - Item props for the current option
       */
      ComboBox.prototype._getCurrentOptionStyles = function (item) {
          var customStylesForAllOptions = this.props.comboBoxOptionStyles;
          var customStylesForCurrentOption = item.styles;
          return ComboBox_styles_1.getOptionStyles(this.props.theme, customStylesForAllOptions, customStylesForCurrentOption, this._isPendingOption(item), item.hidden);
      };
      /**
       * Get the aria-activedescendant value for the comboxbox.
       * @returns the id of the current focused combo item, otherwise the id of the currently selected element, null otherwise
       */
      ComboBox.prototype._getAriaActiveDescentValue = function () {
          var descendantText = this.state.isOpen && this.state.selectedIndices && this.state.selectedIndices.length >= 0
              ? this._id + '-list' + this.state.selectedIndices[0]
              : undefined;
          if (this.state.isOpen && this.state.focused && this.state.currentPendingValueValidIndex !== -1) {
              descendantText = this._id + '-list' + this.state.currentPendingValueValidIndex;
          }
          return descendantText;
      };
      /**
       * Get the aria autocomplete value for the Combobox
       * @returns 'inline' if auto-complete automatically dynamic, 'both' if we have a list of possible values to pick from and can
       * dynamically populate input, and 'none' if auto-complete is not enabled as we can't give user inputs.
       */
      ComboBox.prototype._getAriaAutoCompleteValue = function () {
          var autoComplete = !this.props.disabled && this.props.autoComplete === 'on';
          return autoComplete ? (this.props.allowFreeform ? 'inline' : 'both') : 'none';
      };
      ComboBox.prototype._isPendingOption = function (item) {
          return item && item.index === this.state.currentPendingValueValidIndex;
      };
      /**
       * Given default selected key(s) and selected key(s), return the selected keys(s).
       * When default selected key(s) are available, they take precedence and return them instead of selected key(s).
       *
       * @returns No matter what specific types the input parameters are, always return an array of
       *  either strings or numbers instead of premitive type.  This normlization makes caller's logic easier.
       */
      ComboBox.prototype._buildDefaultSelectedKeys = function (defaultSelectedKey, selectedKey) {
          var selectedKeys = this._buildSelectedKeys(defaultSelectedKey);
          if (selectedKeys.length) {
              return selectedKeys;
          }
          return this._buildSelectedKeys(selectedKey);
      };
      ComboBox.prototype._buildSelectedKeys = function (selectedKey) {
          if (selectedKey === undefined) {
              return [];
          }
          // need to cast here so typescript does not complain
          return (selectedKey instanceof Array ? selectedKey : [selectedKey]);
      };
      // For scenarios where the option's text prop contains embedded styles, we use the option's
      // ariaLabel value as the text in the input and for autocomplete matching. We know to use this
      // when the useAriaLabelAsText prop is set to true
      ComboBox.prototype._getPreviewText = function (item) {
          return item.useAriaLabelAsText && item.ariaLabel ? item.ariaLabel : item.text;
      };
      ComboBox.prototype._normalizeToString = function (value) {
          return value || '';
      };
      ComboBox.prototype._removeZeroWidthSpaces = function (value) {
          // remove any zero width space characters
          return value.replace(RegExp('​', 'g'), '');
      };
      ComboBox.defaultProps = {
          options: [],
          allowFreeform: false,
          autoComplete: 'on',
          buttonIconProps: { iconName: 'ChevronDown' }
      };
      ComboBox = tslib_1.__decorate([
          Utilities_1.customizable('ComboBox', ['theme', 'styles'], true)
      ], ComboBox);
      return ComboBox;
  }(Utilities_1.BaseComponent));
  exports.ComboBox = ComboBox;
  //# sourceMappingURL=ComboBox.js.map