"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var React = require("react");
var DirectionalHint_1 = require("../../common/DirectionalHint");
var Callout_1 = require("../../Callout");
var Label_1 = require("../../Label");
var Button_1 = require("../../Button");
var BaseAutoFill_1 = require("../pickers/AutoFill/BaseAutoFill");
var Utilities_1 = require("../../Utilities");
var SelectableOption_Props_1 = require("../../utilities/selectableOption/SelectableOption.Props");
var stylesImport = require("./ComboBox.scss");
var styles = stylesImport;
var SearchDirection;
(function (SearchDirection) {
    SearchDirection[SearchDirection["backward"] = -1] = "backward";
    SearchDirection[SearchDirection["none"] = 0] = "none";
    SearchDirection[SearchDirection["forward"] = 1] = "forward";
})(SearchDirection || (SearchDirection = {}));
var ComboBox = (function (_super) {
    tslib_1.__extends(ComboBox, _super);
    function ComboBox(props) {
        var _this = _super.call(this, props) || this;
        // This is used to clear any pending autocomplete
        // text (used when autocomplete is true and allowFreeform is false)
        _this._readOnlyPendingAutoCompleteTimeout = 1000 /* ms */;
        _this._warnMutuallyExclusive({
            'defaultSelectedKey': 'selectedKey',
            'value': 'defaultSelectedKey',
            'selectedKey': 'value'
        });
        _this._id = props.id || Utilities_1.getId('ComboBox');
        var selectedKey = props.defaultSelectedKey !== undefined ? props.defaultSelectedKey : props.selectedKey;
        _this._lastReadOnlyAutoCompleteChangeTimeoutId = -1;
        var index = _this._getSelectedIndex(props.options, selectedKey);
        _this.state = {
            isOpen: false,
            selectedIndex: index,
            focused: false,
            suggestedDisplayValue: '',
            currentOptions: _this.props.options,
            currentPendingValueValidIndex: -1,
            currentPendingValue: ''
        };
        return _this;
    }
    ComboBox.prototype.componentDidMount = function () {
        // hook up resolving the options if needed on focus
        this._events.on(this._comboBoxWrapper, 'focus', this._onResolveOptions, true);
    };
    ComboBox.prototype.componentWillReceiveProps = function (newProps) {
        // In controlled component usage where selectedKey is provided, update the selectedIndex
        // and currentOptions state if the key or options change
        if (newProps.selectedKey !== undefined &&
            (newProps.selectedKey !== this.props.selectedKey || newProps.options !== this.props.options)) {
            var index = this._getSelectedIndex(newProps.options, newProps.selectedKey);
            this.setState({
                selectedIndex: index,
                currentOptions: newProps.options
            });
        }
    };
    ComboBox.prototype.componentDidUpdate = function (prevProps, prevState) {
        var allowFreeform = this.props.allowFreeform;
        var _a = this.state, isOpen = _a.isOpen, focused = _a.focused, selectedIndex = _a.selectedIndex;
        // If we are open, make sure the currently
        // selected option is scrolled into view
        if (isOpen) {
            this._scrollIntoView();
        }
        // If we are open or we are focused but are not the activeElement,
        // set focus on the input
        if (isOpen || (focused && document.activeElement !== this._comboBox.inputElement)) {
            this.focus();
        }
        // If we just opened/closed the menu OR
        // updated the selectedIndex with the menu closed OR
        // we are focused and are not allowing freeform
        // we need to fix up focus and set selection
        if (prevState.isOpen !== isOpen ||
            (!isOpen && prevState.selectedIndex !== selectedIndex) ||
            (!allowFreeform && focused)) {
            this._select();
        }
    };
    ComboBox.prototype.componentWillUnmount = function () {
        _super.prototype.componentWillUnmount.call(this);
        // remove the eventHanlder that was added in componentDidMount
        this._events.off(this._comboBoxWrapper);
    };
    // Primary Render
    ComboBox.prototype.render = function () {
        var id = this._id;
        var _a = this.props, className = _a.className, label = _a.label, disabled = _a.disabled, ariaLabel = _a.ariaLabel, required = _a.required, errorMessage = _a.errorMessage, _b = _a.onRenderContainer, onRenderContainer = _b === void 0 ? this._onRenderContainer : _b, allowFreeform = _a.allowFreeform, autoComplete = _a.autoComplete, buttonIconProps = _a.buttonIconProps;
        var _c = this.state, isOpen = _c.isOpen, selectedIndex = _c.selectedIndex, focused = _c.focused, suggestedDisplayValue = _c.suggestedDisplayValue;
        this._currentVisibleValue = this._getVisibleValue();
        var divProps = Utilities_1.getNativeProps(this.props, Utilities_1.divProperties);
        return (React.createElement("div", tslib_1.__assign({}, divProps, { ref: 'root', className: Utilities_1.css('ms-ComboBox-container') }),
            label && (React.createElement(Label_1.Label, { id: id + '-label', required: required, htmlFor: id }, label)),
            React.createElement("div", { ref: this._resolveRef('_comboBoxWrapper'), id: id + 'wrapper', className: Utilities_1.css('ms-ComboBox', styles.wrapper, (errorMessage && errorMessage.length > 0 ? styles.wrapperForError : null), styles.root, className, (_d = {
                        'is-open': isOpen
                    },
                    _d['is-disabled ' + styles.rootIsDisabled] = disabled,
                    _d['is-required '] = required,
                    _d[styles.focused] = focused,
                    _d[styles.readOnly] = !allowFreeform,
                    _d)) },
                React.createElement(BaseAutoFill_1.BaseAutoFill, { "data-is-interactable": !disabled, ref: this._resolveRef('_comboBox'), id: id + '-input', className: Utilities_1.css('ms-ComboBox-Input', styles.input), type: 'text', key: selectedIndex, onFocus: this._select, onBlur: this._onBlur, onKeyDown: this._onInputKeyDown, onKeyUp: this._onInputKeyUp, onClick: allowFreeform ? this.focus : this._onComboBoxClick, onInputValueChange: this._onInputChange, "aria-expanded": isOpen, "aria-autocomplete": (!disabled && autoComplete), role: 'combobox', "aria-readonly": ((allowFreeform || disabled) ? null : 'true'), readOnly: disabled || !allowFreeform, "aria-labelledby": (label && (id + '-label')), "aria-label": ((ariaLabel && !label) && ariaLabel), "aria-describedby": (id + '-option'), "aria-activedescendant": (isOpen && selectedIndex >= 0 ? (id + '-list' + selectedIndex) : null), "aria-disabled": disabled, "aria-owns": (id + '-list'), spellCheck: false, defaultVisibleValue: this._currentVisibleValue, suggestedDisplayValue: suggestedDisplayValue, updateValueInWillReceiveProps: this._onUpdateValueInAutoFillWillReceiveProps, shouldSelectFullInputValueInComponentDidUpdate: this._onShouldSelectFullInputValueInAutoFillComponentDidUpdate }),
                React.createElement(Button_1.IconButton, { className: Utilities_1.css('ms-ComboBox-Button', styles.caretDown), role: 'presentation', "aria-hidden": 'true', tabIndex: -1, onClick: this._onComboBoxClick, iconProps: buttonIconProps, disabled: disabled })),
            isOpen && (onRenderContainer(tslib_1.__assign({}, this.props), this._onRenderContainer)),
            errorMessage &&
                React.createElement("div", { className: Utilities_1.css(styles.errorMessage) }, errorMessage)));
        var _d;
    };
    /**
     * Set focus on the input
     */
    ComboBox.prototype.focus = function () {
        if (this._comboBox) {
            this._comboBox.focus();
        }
    };
    /**
     * componentWillReceiveProps handler for the auto fill component
     * Checks/updates the iput value to set, if needed
     * @param {IBaseAutoFillProps} defaultVisibleValue - the defaultVisibleValue that got passed
     *  in to the auto fill's componentWillReceiveProps
     * @returns {string} - the updated value to set, if needed
     */
    ComboBox.prototype._onUpdateValueInAutoFillWillReceiveProps = function () {
        if (this._comboBox === null || this._comboBox === undefined) {
            return null;
        }
        if (this._currentVisibleValue && this._currentVisibleValue !== '' && this._comboBox.value !== this._currentVisibleValue) {
            return this._currentVisibleValue;
        }
        return this._comboBox.value;
    };
    /**
     * componentDidUpdate handler for the auto fill component
     *
     * @param { string } defaultVisibleValue - the current defaultVisibleValue in the auto fill's componentDidUpdate
     * @param { string } suggestedDisplayValue - the current suggestedDisplayValue in the auto fill's componentDidUpdate
     * @returns {boolean} - should the full value of the input be selected?
     * True if the defaultVisibleValue equals the suggestedDisplayValue, false otherwise
     */
    ComboBox.prototype._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 {string} the value to pass to the input
     */
    ComboBox.prototype._getVisibleValue = function () {
        var _a = this.props, value = _a.value, allowFreeform = _a.allowFreeform, autoComplete = _a.autoComplete;
        var _b = this.state, selectedIndex = _b.selectedIndex, currentPendingValueValidIndex = _b.currentPendingValueValidIndex, currentOptions = _b.currentOptions, currentPendingValue = _b.currentPendingValue, suggestedDisplayValue = _b.suggestedDisplayValue;
        // If the user passed is a value prop, use that
        if (value) {
            return value;
        }
        var index = selectedIndex;
        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 && this._indexWithinBounds(currentOptions, currentPendingValueValidIndex)) {
                index = currentPendingValueValidIndex;
            }
            // Since we are allowing freeform, if there is currently a nonempty pending value, use that
            // otherwise use the index determined above (falling back to '' if we did not get a valid index)
            return currentPendingValue !== '' ? 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 (this._indexWithinBounds(currentOptions, currentPendingValueValidIndex)) {
                // If autoComplete is on, return the
                // raw pending value, otherwise remember
                // the matched option's index
                if (autoComplete) {
                    return currentPendingValue;
                }
                index = currentPendingValueValidIndex;
            }
            // If we have a valid index then return the text value of that option,
            // otherwise return the suggestedDisplayValue
            return this._indexWithinBounds(currentOptions, index) ? currentOptions[index].text : suggestedDisplayValue;
        }
    };
    /**
     * 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 {boolean} - true if the index is valid for the given options, false otherwise
     */
    ComboBox.prototype._indexWithinBounds = function (options, index) {
        return index >= 0 && index < options.length;
    };
    /**
     * Handler for typing changes on the input
     * @param updatedValue - the newly changed value
     */
    ComboBox.prototype._onInputChange = function (updatedValue) {
        if (this.props.disabled) {
            this._handleInputWhenDisabled(null /* event */);
            return;
        }
        this.props.allowFreeform ?
            this._processInputChangeWithFreeform(updatedValue) :
            this._processInputChangeWithoutFreeform(updatedValue);
    };
    /**
     * 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 currentOptions = this.state.currentOptions;
        // if the new value is empty, nothing needs to be done
        if (updatedValue === '') {
            return;
        }
        // Remember the original value and then,
        // make the value lowercase for comparison
        var originalUpdatedValue = updatedValue;
        updatedValue = updatedValue.toLocaleLowerCase();
        var newSuggestedDisplayValue = '';
        var newCurrentPendingValueValidIndex = -1;
        // If autoComplete is on, attempt to find a match from the available options
        if (this.props.autoComplete) {
            // 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 !== SelectableOption_Props_1.SelectableOptionMenuItemType.Header && option.itemType !== SelectableOption_Props_1.SelectableOptionMenuItemType.Divider; }).filter(function (option) { return option.text.toLocaleLowerCase().indexOf(updatedValue) === 0; });
            if (items.length > 0) {
                // If the user typed out the complete option text, we don't need any suggested display text anymore
                newSuggestedDisplayValue = items[0].text.toLocaleLowerCase() !== updatedValue ? items[0].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 !== SelectableOption_Props_1.SelectableOptionMenuItemType.Header && option.itemType !== SelectableOption_Props_1.SelectableOptionMenuItemType.Divider; }).filter(function (option) { return option.text.toLocaleLowerCase() === updatedValue; });
            // if we fould 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, selectedIndex = _a.selectedIndex;
        if (this.props.autoComplete) {
            // 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 > 0) {
                    this._async.clearTimeout(this._lastReadOnlyAutoCompleteChangeTimeoutId);
                    this._lastReadOnlyAutoCompleteChangeTimeoutId = -1;
                    updatedValue = 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, index) { return tslib_1.__assign({}, item, { index: index }); }).filter(function (option) { return option.itemType !== SelectableOption_Props_1.SelectableOptionMenuItemType.Header && option.itemType !== SelectableOption_Props_1.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, items[0].text);
                }
                // Schedule a timeout to clear the pending value after the timeout span
                this._lastReadOnlyAutoCompleteChangeTimeoutId =
                    this._async.setTimeout(function () { _this._lastReadOnlyAutoCompleteChangeTimeoutId = -1; }, this._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 : selectedIndex;
        // 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);
    };
    /**
     * 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 {number} - 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));
        var option = currentOptions[newIndex];
        // attempt to skip headers and dividers
        if ((option.itemType === SelectableOption_Props_1.SelectableOptionMenuItemType.Header ||
            option.itemType === SelectableOption_Props_1.SelectableOptionMenuItemType.Divider)) {
            // Should we continue looking for an index to select?
            if (searchDirection !== SearchDirection.none &&
                ((newIndex !== 0 && searchDirection < SearchDirection.none) ||
                    (newIndex !== currentOptions.length - 1 && 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, searchDirection) {
        if (searchDirection === void 0) { searchDirection = SearchDirection.none; }
        var onChanged = this.props.onChanged;
        var _a = this.state, selectedIndex = _a.selectedIndex, currentOptions = _a.currentOptions;
        // Find the next selectable index, if searchDirection is none
        // we will get our starting index back
        index = this._getNextSelectableIndex(index, searchDirection);
        // Are we at a new index? If so, update the state, otherwise
        // there is nothing to do
        if (index !== selectedIndex) {
            var option = currentOptions[index];
            // Set the selected option
            this.setState({
                selectedIndex: index
            });
            // Did the creator give us an onChanged callback?
            if (onChanged) {
                onChanged(option, index);
            }
            // if we have a new selected index,
            // clear all of the pending info
            this._clearPendingInfo();
        }
    };
    /**
     * Focus (and select) the content of the input
     * and set the focused state
     */
    ComboBox.prototype._select = function () {
        this._comboBox.inputElement.select();
        if (!this.state.focused) {
            this.setState({ focused: true });
        }
    };
    /**
     * 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
     */
    ComboBox.prototype._onResolveOptions = function () {
        var _this = this;
        if (this.props.onResolveOptions) {
            // get the options
            var newOptions = this.props.onResolveOptions(tslib_1.__assign({}, this.state.currentOptions));
            // 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
     */
    ComboBox.prototype._onBlur = function () {
        if (this.state.focused) {
            this.setState({ focused: false });
            this._submitPendingValue();
        }
    };
    /**
     * Submit a pending value if there is one
     */
    ComboBox.prototype._submitPendingValue = function () {
        var _a = this.props, onChanged = _a.onChanged, allowFreeform = _a.allowFreeform;
        var _b = this.state, currentPendingValue = _b.currentPendingValue, currentPendingValueValidIndex = _b.currentPendingValueValidIndex, currentOptions = _b.currentOptions;
        // If we allow freeform and we have a pending value, we
        // need to handle that
        if (allowFreeform && currentPendingValue !== '') {
            // Check to see if the user typed an exact match
            if (currentPendingValueValidIndex >= 0) {
                var pendingOptionText = currentOptions[currentPendingValueValidIndex].text.toLocaleLowerCase();
                // By exact match, that means: our pending value is the same as the the pending option text OR
                // the peding option starts with the pending value and we have an "autoComplete" selection
                // where the total lenght is equal to pending option length; update the state
                if (currentPendingValue.toLocaleLowerCase() === pendingOptionText ||
                    (pendingOptionText.indexOf(currentPendingValue.toLocaleLowerCase()) === 0 &&
                        this._comboBox.isValueSelected &&
                        currentPendingValue.length + (this._comboBox.selectionEnd - this._comboBox.selectionStart) === pendingOptionText.length)) {
                    this._setSelectedIndex(currentPendingValueValidIndex);
                    this._clearPendingInfo();
                    return;
                }
            }
            // Create a new option
            var newOption = { key: currentPendingValue, text: currentPendingValue };
            var newOptions = currentOptions.concat([newOption]);
            var newSelectedIndex = this._getSelectedIndex(newOptions, currentPendingValue);
            this.setState({
                currentOptions: newOptions,
                selectedIndex: newSelectedIndex
            });
            if (onChanged) {
                onChanged(null, null, currentPendingValue);
            }
        }
        else if (currentPendingValueValidIndex >= 0) {
            // Since we are not allowing freeform, we must have a matching
            // to be able to update state
            this._setSelectedIndex(currentPendingValueValidIndex);
        }
        // Finally, clear the pending info
        this._clearPendingInfo();
    };
    // Render Callout container and pass in list
    ComboBox.prototype._onRenderContainer = function (props) {
        var _a = props.onRenderList, onRenderList = _a === void 0 ? this._onRenderList : _a, calloutProps = props.calloutProps;
        return (React.createElement(Callout_1.Callout, tslib_1.__assign({ isBeakVisible: false, gapSpace: 0, doNotLayer: false, directionalHint: DirectionalHint_1.DirectionalHint.bottomLeftEdge, directionalHintFixed: true }, calloutProps, { className: Utilities_1.css('ms-ComboBox-callout', styles.callout, calloutProps ? calloutProps.className : undefined), targetElement: this._comboBoxWrapper, onDismiss: this._onDismiss, setInitialFocus: false }),
            React.createElement("div", { ref: this._resolveRef('_comboBoxMenu'), style: { width: this._comboBoxWrapper.clientWidth - 2 } }, onRenderList(tslib_1.__assign({}, props), this._onRenderList))));
    };
    // Render List of items
    ComboBox.prototype._onRenderList = function (props) {
        var _this = this;
        var _a = this.props.onRenderItem, onRenderItem = _a === void 0 ? this._onRenderItem : _a;
        var id = this._id;
        var selectedIndex = this.state.selectedIndex;
        return (React.createElement("div", { id: id + '-list', className: Utilities_1.css('ms-ComboBox-items', styles.items), "aria-labelledby": id + '-label', role: 'listbox' }, this.state.currentOptions.map(function (item, index) { return onRenderItem(tslib_1.__assign({}, item, { index: index }), _this._onRenderItem); })));
    };
    // Render items
    ComboBox.prototype._onRenderItem = function (item) {
        switch (item.itemType) {
            case SelectableOption_Props_1.SelectableOptionMenuItemType.Divider:
                return this._renderSeparator(item);
            case SelectableOption_Props_1.SelectableOptionMenuItemType.Header:
                return this._renderHeader(item);
            default:
                return this._renderOption(item);
        }
    };
    // Render separator
    ComboBox.prototype._renderSeparator = function (item) {
        var index = item.index, key = item.key;
        if (index > 0) {
            return React.createElement("div", { role: 'separator', key: key, className: Utilities_1.css('ms-ComboBox-divider', styles.divider) });
        }
        return null;
    };
    ComboBox.prototype._renderHeader = function (item) {
        var _a = this.props.onRenderOption, onRenderOption = _a === void 0 ? this._onRenderOption : _a;
        return (React.createElement("div", { key: item.key, className: Utilities_1.css('ms-ComboBox-header', styles.header), role: 'header' }, onRenderOption(item, this._onRenderOption)));
    };
    // Render menu item
    ComboBox.prototype._renderOption = function (item) {
        var _this = this;
        var _a = this.props.onRenderOption, onRenderOption = _a === void 0 ? this._onRenderOption : _a;
        var id = this._id;
        var isSelected = this._isOptionSelected(item.index);
        return (React.createElement(Button_1.CommandButton, { id: id + '-list' + item.index, key: item.key, "data-index": item.index, className: Utilities_1.css('ms-ComboBox-item', styles.item, (_b = {},
                _b['is-selected ' + styles.itemIsSelected] = isSelected,
                _b['is-disabled ' + styles.itemIsDisabled] = this.props.disabled === true,
                _b)), onClick: function () { return _this._onItemClick(item.index); }, role: 'option', "aria-selected": isSelected ? 'true' : 'false', ariaLabel: item.text },
            " ",
            React.createElement("span", { ref: this._resolveRef(isSelected ? '_selectedElement' : '') }, onRenderOption(item, this._onRenderOption))));
        var _b;
    };
    /**
     * 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 _a = this.state, currentPendingValueValidIndex = _a.currentPendingValueValidIndex, currentPendingValue = _a.currentPendingValue, selectedIndex = _a.selectedIndex;
        return ((currentPendingValueValidIndex >= 0 || currentPendingValue !== '') ?
            currentPendingValueValidIndex === index : selectedIndex === index);
    };
    /**
     * Scroll the selected element into view
     */
    ComboBox.prototype._scrollIntoView = function () {
        if (this._selectedElement) {
            var alignToTop = true;
            if (this._comboBoxMenu.offsetParent) {
                var scrollableParentRect = this._comboBoxMenu.offsetParent.getBoundingClientRect();
                var selectedElementRect = this._selectedElement.offsetParent.getBoundingClientRect();
                if (scrollableParentRect.top + scrollableParentRect.height <= selectedElementRect.top) {
                    alignToTop = false;
                }
            }
            this._selectedElement.offsetParent.scrollIntoView(alignToTop);
        }
    };
    // Render content of item
    ComboBox.prototype._onRenderOption = function (item) {
        return React.createElement("span", { className: Utilities_1.css('ms-ComboBox-optionText', styles.optionText) }, item.text);
    };
    /**
     * 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 (index) {
        this._setSelectedIndex(index);
        this.setState({
            isOpen: false
        });
    };
    /**
     * Handles dismissing (cancelling) the menu
     */
    ComboBox.prototype._onDismiss = function () {
        // reset the selected index
        // to the last valud state
        this._resetSelectedIndex();
        // close the menu and focus the input
        this.setState({ isOpen: false });
        this._comboBox.focus();
    };
    /**
     * Get the index of the option that is marked as selected
     * @param options - the comboBox options
     * @param selectedKey - the known selected key to find
     * @returns {number} - the index of the selected option, -1 if not found
     */
    ComboBox.prototype._getSelectedIndex = function (options, selectedKey) {
        return Utilities_1.findIndex(options, (function (option) { return (option.isSelected || option.selected || (selectedKey != null) && option.key === selectedKey); }));
    };
    /**
     * 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 _a = this.state, selectedIndex = _a.selectedIndex, currentOptions = _a.currentOptions;
        this._comboBox.clear();
        this._clearPendingInfo();
        if (selectedIndex > 0 && selectedIndex < currentOptions.length) {
            this.setState({
                suggestedDisplayValue: currentOptions[selectedIndex].text
            });
        }
    };
    /**
     * Clears the pending info state
     */
    ComboBox.prototype._clearPendingInfo = function () {
        this._setPendingInfo('' /* suggestedDisplayValue */, -1 /* currentPendingValueValidIndex */, '' /* currentPendingValue */);
    };
    /**
     * 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) {
        this.setState({
            currentPendingValue: currentPendingValue,
            currentPendingValueValidIndex: currentPendingValueValidIndex,
            suggestedDisplayValue: suggestedDisplayValue
        });
    };
    /**
     * 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(option.text, index, option.text);
        }
        else {
            this._clearPendingInfo();
        }
    };
    /**
     * Sets either the pending info or the
     * selected index depending of if the comboBox is open
     * @param index - the index to search from
     * @param searchDirection - the direction to search
     */
    ComboBox.prototype._setInfoForIndexAndDirection = function (index, searchDirection) {
        var _a = this.state, isOpen = _a.isOpen, selectedIndex = _a.selectedIndex;
        if (isOpen) {
            index = this._getNextSelectableIndex(index, searchDirection);
            this._setPendingInfoFromIndex(index);
        }
        else {
            this._setSelectedIndex(selectedIndex, searchDirection);
        }
    };
    /**
     * Handle keydown on the input
     * @param ev - The keyboard event that was fired
     */
    ComboBox.prototype._onInputKeyDown = function (ev) {
        var _a = this.props, disabled = _a.disabled, allowFreeform = _a.allowFreeform, autoComplete = _a.autoComplete;
        var _b = this.state, isOpen = _b.isOpen, currentPendingValueValidIndex = _b.currentPendingValueValidIndex, selectedIndex = _b.selectedIndex, currentOptions = _b.currentOptions;
        if (disabled) {
            this._handleInputWhenDisabled(ev);
            return;
        }
        var index = currentPendingValueValidIndex >= 0 ? currentPendingValueValidIndex : selectedIndex;
        switch (ev.which) {
            case Utilities_1.KeyCodes.enter:
                // On enter submit the pending value
                this._submitPendingValue();
                // 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
                if ((isOpen ||
                    ((!allowFreeform ||
                        this.state.currentPendingValue === undefined ||
                        this.state.currentPendingValue === null ||
                        this.state.currentPendingValue.length <= 0) &&
                        this.state.currentPendingValueValidIndex < 0))) {
                    this.setState({
                        isOpen: !isOpen
                    });
                }
                // Allow TAB to propigate
                if (ev.which === Utilities_1.KeyCodes.tab) {
                    return;
                }
                break;
            case Utilities_1.KeyCodes.tab:
                // On enter submit the pending value
                this._submitPendingValue();
                // If we are not allowing freeform
                // or the comboBox is open, flip the open state
                if (isOpen) {
                    this.setState({
                        isOpen: !isOpen
                    });
                }
                // 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
                    });
                }
                break;
            case Utilities_1.KeyCodes.up:
                // Go to the previous option
                this._setInfoForIndexAndDirection(index, SearchDirection.backward);
                break;
            case Utilities_1.KeyCodes.down:
                // Expand the comboBox on ALT + DownArrow
                if (ev.altKey || ev.metaKey) {
                    this.setState({ isOpen: true });
                }
                else {
                    // Got to the next option
                    this._setInfoForIndexAndDirection(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._setInfoForIndexAndDirection(index, directionToSearch);
                break;
            case Utilities_1.KeyCodes.space:
                // event handled in _onComboBoxKeyUp
                if (!allowFreeform && !autoComplete) {
                    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 and we are current open, let's close the menu
                if ((ev.altKey || ev.metaKey) && isOpen) {
                    this.setState({ isOpen: !isOpen });
                }
                // If we are not allowing freeform and
                // allowing autoComplete, handle the input here
                // since we have marked the input as readonly
                if (!allowFreeform && autoComplete) {
                    this._onInputChange(String.fromCharCode(ev.which));
                    break;
                }
                // allow the key to propigate by default
                return;
        }
        ev.stopPropagation();
        ev.preventDefault();
    };
    /**
     * Handle keyup on the input
     * @param ev - the keyboard event that was fired
     */
    ComboBox.prototype._onInputKeyUp = function (ev) {
        var _a = this.props, disabled = _a.disabled, allowFreeform = _a.allowFreeform, autoComplete = _a.autoComplete;
        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) {
                    this.setState({
                        isOpen: !this.state.isOpen
                    });
                    return;
                }
                break;
            default:
                return;
        }
        ev.stopPropagation();
        ev.preventDefault();
    };
    /**
     * 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();
            }
        }
    };
    /**
     * 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)
     */
    ComboBox.prototype._onComboBoxClick = function () {
        var disabled = this.props.disabled;
        var isOpen = this.state.isOpen;
        if (!disabled) {
            this.setState({
                isOpen: !isOpen
            });
        }
    };
    return ComboBox;
}(Utilities_1.BaseComponent));
ComboBox.defaultProps = {
    options: [],
    allowFreeform: false,
    autoComplete: true,
    buttonIconProps: { iconName: 'ChevronDown' }
};
tslib_1.__decorate([
    Utilities_1.autobind
], ComboBox.prototype, "focus", null);
tslib_1.__decorate([
    Utilities_1.autobind
], ComboBox.prototype, "_onUpdateValueInAutoFillWillReceiveProps", null);
tslib_1.__decorate([
    Utilities_1.autobind
], ComboBox.prototype, "_onShouldSelectFullInputValueInAutoFillComponentDidUpdate", null);
tslib_1.__decorate([
    Utilities_1.autobind
], ComboBox.prototype, "_getVisibleValue", null);
tslib_1.__decorate([
    Utilities_1.autobind
], ComboBox.prototype, "_onInputChange", null);
tslib_1.__decorate([
    Utilities_1.autobind
], ComboBox.prototype, "_select", null);
tslib_1.__decorate([
    Utilities_1.autobind
], ComboBox.prototype, "_onResolveOptions", null);
tslib_1.__decorate([
    Utilities_1.autobind
], ComboBox.prototype, "_onBlur", null);
tslib_1.__decorate([
    Utilities_1.autobind
], ComboBox.prototype, "_onRenderContainer", null);
tslib_1.__decorate([
    Utilities_1.autobind
], ComboBox.prototype, "_onRenderList", null);
tslib_1.__decorate([
    Utilities_1.autobind
], ComboBox.prototype, "_onRenderItem", null);
tslib_1.__decorate([
    Utilities_1.autobind
], ComboBox.prototype, "_renderOption", null);
tslib_1.__decorate([
    Utilities_1.autobind
], ComboBox.prototype, "_onRenderOption", null);
tslib_1.__decorate([
    Utilities_1.autobind
], ComboBox.prototype, "_onDismiss", null);
tslib_1.__decorate([
    Utilities_1.autobind
], ComboBox.prototype, "_onInputKeyDown", null);
tslib_1.__decorate([
    Utilities_1.autobind
], ComboBox.prototype, "_onInputKeyUp", null);
tslib_1.__decorate([
    Utilities_1.autobind
], ComboBox.prototype, "_onComboBoxClick", null);
exports.ComboBox = ComboBox;

//# sourceMappingURL=ComboBox.js.map
