import { __assign, __decorate, __extends, __rest, __spreadArray } from "tslib";
import * as React from 'react';
import { Autofill } from '../../Autofill';
import { initializeComponentRef, css, customizable, divProperties, findElementRecursive, findIndex, focusAsync, getId, getNativeProps, isIOS, isMac, KeyCodes, shallowCompare, mergeAriaAttributeValues, warnMutuallyExclusive, Async, EventGroup, getPropsWithDefaults, getRootNode, getChildren, getActiveElement, } from '../../Utilities';
import { Callout, DirectionalHint } from '../../Callout';
import { Checkbox } from '../../Checkbox';
import { getCaretDownButtonStyles, getOptionStyles, getStyles } from './ComboBox.styles';
import { getClassNames, getComboBoxOptionClassNames } from './ComboBox.classNames';
import { Label } from '../../Label';
import { SelectableOptionMenuItemType, getAllSelectedOptions } from '../../SelectableOption';
import { CommandButton, IconButton } from '../../Button';
import { useMergedRefs } from '@fluentui/react-hooks';
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 component that is used to wrap all ComboBox options.
 * This is used to customize when we want to re-render components,
 * so we don't re-render every option every time render is executed.
 */
var ComboBoxOptionWrapper = React.memo(function (_a) {
    var render = _a.render;
    return render();
}, function (_a, _b) {
    var oldRender = _a.render, oldProps = __rest(_a, ["render"]);
    var newRender = _b.render, newProps = __rest(_b, ["render"]);
    // The render function will always be different, so we ignore that prop
    return shallowCompare(oldProps, newProps);
});
var COMPONENT_NAME = 'ComboBox';
var DEFAULT_PROPS = {
    options: [],
    allowFreeform: false,
    autoComplete: 'on',
    buttonIconProps: { iconName: 'ChevronDown' },
};
function useOptionsState(_a) {
    var options = _a.options, defaultSelectedKey = _a.defaultSelectedKey, selectedKey = _a.selectedKey;
    /** The currently selected indices */
    var _b = React.useState(function () {
        return getSelectedIndices(options, buildDefaultSelectedKeys(defaultSelectedKey, selectedKey));
    }), selectedIndices = _b[0], setSelectedIndices = _b[1];
    /** The options currently available for the callout */
    var _c = React.useState(options), currentOptions = _c[0], setCurrentOptions = _c[1];
    /** This value is used for the autocomplete hint value */
    var _d = React.useState(), suggestedDisplayValue = _d[0], setSuggestedDisplayValue = _d[1];
    React.useEffect(function () {
        if (selectedKey !== undefined) {
            var selectedKeys = buildSelectedKeys(selectedKey);
            var indices = getSelectedIndices(options, selectedKeys);
            setSelectedIndices(indices);
        }
        setCurrentOptions(options);
    }, [options, selectedKey]);
    React.useEffect(function () {
        if (selectedKey === null) {
            setSuggestedDisplayValue(undefined);
        }
    }, [selectedKey]);
    return [
        selectedIndices,
        setSelectedIndices,
        currentOptions,
        setCurrentOptions,
        suggestedDisplayValue,
        setSuggestedDisplayValue,
    ];
}
export var ComboBox = React.forwardRef(function (propsWithoutDefaults, forwardedRef) {
    var _a = getPropsWithDefaults(DEFAULT_PROPS, propsWithoutDefaults), ref = _a.ref, props = __rest(_a, ["ref"]);
    var rootRef = React.useRef(null);
    var mergedRootRef = useMergedRefs(rootRef, forwardedRef);
    var _b = useOptionsState(props), selectedIndices = _b[0], setSelectedIndices = _b[1], currentOptions = _b[2], setCurrentOptions = _b[3], suggestedDisplayValue = _b[4], setSuggestedDisplayValue = _b[5];
    return (React.createElement(ComboBoxInternal, __assign({}, props, { hoisted: {
            mergedRootRef: mergedRootRef,
            rootRef: rootRef,
            selectedIndices: selectedIndices,
            setSelectedIndices: setSelectedIndices,
            currentOptions: currentOptions,
            setCurrentOptions: setCurrentOptions,
            suggestedDisplayValue: suggestedDisplayValue,
            setSuggestedDisplayValue: setSuggestedDisplayValue,
        } })));
});
ComboBox.displayName = COMPONENT_NAME;
/**
 * Depth-first search to find the first descendant element where the match function returns true.
 * @param element - element to start searching at
 * @param match - the function that determines if the element is a match
 * @returns the matched element or null no match was found
 */
function findFirstDescendant(element, match) {
    var children = getChildren(element);
    // For loop is used because forEach cannot be stopped.
    for (var index = 0; index < children.length; index++) {
        var child = children[index];
        if (match(child)) {
            return child;
        }
        var candidate = findFirstDescendant(child, match);
        if (candidate) {
            return candidate;
        }
    }
    return null;
}
var ComboBoxInternal = /** @class */ (function (_super) {
    __extends(ComboBoxInternal, _super);
    function ComboBoxInternal(props) {
        var _this = _super.call(this, props) || this;
        /** The input aspect of the combo box */
        _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.props.disabled) {
                return;
            }
            if (_this._autofill.current) {
                if (useFocusAsync) {
                    focusAsync(_this._autofill.current);
                }
                else {
                    _this._autofill.current.focus();
                }
                if (shouldOpenOnFocus) {
                    _this.setState({
                        isOpen: true,
                    });
                }
            }
            // Programmatically setting focus means that there is nothing else that needs to be done
            // Focus is now contained
            if (!_this._hasFocus()) {
                _this.setState({ focusState: 'focused' });
            }
        };
        /**
         * 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 input 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 = normalizeToString(_this._currentVisibleValue);
            if (comboBox.value !== visibleValue) {
                return visibleValue;
            }
            return comboBox.value;
        };
        _this._renderComboBoxWrapper = function (multiselectAccessibleText, errorMessageId) {
            var _a = _this.props, label = _a.label, disabled = _a.disabled, ariaLabel = _a.ariaLabel, _b = _a.ariaDescribedBy, ariaDescribedBy = _b === void 0 ? _this.props['aria-describedby'] : _b, required = _a.required, errorMessage = _a.errorMessage, buttonIconProps = _a.buttonIconProps, _c = _a.isButtonAriaHidden, isButtonAriaHidden = _c === void 0 ? true : _c, title = _a.title, placeholderProp = _a.placeholder, tabIndex = _a.tabIndex, autofill = _a.autofill, iconButtonProps = _a.iconButtonProps, suggestedDisplayValue = _a.hoisted.suggestedDisplayValue;
            var isOpen = _this.state.isOpen;
            // If the combo box has focus, is multiselect, and has a display string, then use that placeholder
            // so that the selected items don't appear to vanish. This is not ideal but it's the only reasonable way
            // to correct the behavior where the input is cleared so the user can type. If a full refactor is done, then this
            // should be removed and the multiselect combo box should behave like a picker.
            var placeholder = _this._hasFocus() && _this.props.multiSelect && multiselectAccessibleText
                ? multiselectAccessibleText
                : placeholderProp;
            var labelledBy = [_this.props['aria-labelledby'], label && _this._id + '-label'].join(' ').trim();
            return (React.createElement("div", { "data-ktp-target": true, ref: _this._comboBoxWrapper, id: _this._id + 'wrapper', className: _this._classNames.root, "aria-owns": isOpen ? _this._id + '-list' : undefined },
                React.createElement(Autofill, __assign({ "data-ktp-execute-target": true, "data-is-interactable": !disabled, componentRef: _this._autofill, id: _this._id + '-input', className: _this._classNames.input, type: "text", onFocus: _this._onFocus, 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, "aria-labelledby": labelledBy ? labelledBy : undefined, "aria-label": ariaLabel && !label ? ariaLabel : undefined, "aria-describedby": errorMessage !== undefined ? mergeAriaAttributeValues(ariaDescribedBy, errorMessageId) : ariaDescribedBy, "aria-activedescendant": _this._getAriaActiveDescendantValue(), "aria-required": required, "aria-disabled": disabled, "aria-controls": isOpen ? _this._id + '-list' : undefined, spellCheck: false, defaultVisibleValue: _this._currentVisibleValue, suggestedDisplayValue: suggestedDisplayValue, updateValueInWillReceiveProps: _this._onUpdateValueInAutofillWillReceiveProps, shouldSelectFullInputValueInComponentDidUpdate: _this._onShouldSelectFullInputValueInAutofillComponentDidUpdate, title: title, preventValueSelection: !_this._hasFocus(), placeholder: placeholder, tabIndex: disabled ? -1 : tabIndex }, autofill)),
                React.createElement(IconButton, __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))));
        };
        /**
         * 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.props.hoisted.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, _b = _a.hoisted, suggestedDisplayValue = _b.suggestedDisplayValue, selectedIndices = _b.selectedIndices, currentOptions = _b.currentOptions;
            var _c = _this.state, currentPendingValueValidIndex = _c.currentPendingValueValidIndex, currentPendingValue = _c.currentPendingValue, isOpen = _c.isOpen;
            var currentPendingIndexValid = 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;
            }
            if (_this.props.multiSelect) {
                // Multi-select
                if (_this._hasFocus()) {
                    var index = -1;
                    if (autoComplete === 'on' && currentPendingIndexValid) {
                        index = currentPendingValueValidIndex;
                    }
                    return _this._getPendingString(currentPendingValue, currentOptions, index);
                }
                else {
                    return _this._getMultiselectDisplayString(selectedIndices, currentOptions, 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)
                    return _this._getPendingString(currentPendingValue, currentOptions, index);
                }
                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;
                        return normalizeToString(currentPendingValue);
                    }
                    else if (!_this.state.isOpen && currentPendingValue) {
                        return indexWithinBounds(currentOptions, index)
                            ? currentPendingValue
                            : normalizeToString(suggestedDisplayValue);
                    }
                    else {
                        return indexWithinBounds(currentOptions, index)
                            ? getPreviewText(currentOptions[index])
                            : normalizeToString(suggestedDisplayValue);
                    }
                }
            }
        };
        /**
         * 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;
            }
            if (_this.props.onInputValueChange) {
                _this.props.onInputValueChange(updatedValue);
            }
            _this.props.allowFreeform
                ? _this._processInputChangeWithFreeform(updatedValue)
                : _this._processInputChangeWithoutFreeform(updatedValue);
        };
        /**
         * Focus (and select) the content of the input
         * and set the focused state
         */
        _this._onFocus = function () {
            var _a, _b;
            (_b = (_a = _this._autofill.current) === null || _a === void 0 ? void 0 : _a.inputElement) === null || _b === void 0 ? void 0 : _b.select();
            if (!_this._hasFocus()) {
                _this.setState({ focusState: 'focusing' });
            }
        };
        /**
         * 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_1 = _this.props.onResolveOptions(__spreadArray([], _this.props.hoisted.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_1)) {
                    _this.props.hoisted.setCurrentOptions(newOptions_1);
                }
                else if (newOptions_1 && newOptions_1.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
                    _this._currentPromise = newOptions_1;
                    newOptions_1.then(function (newOptionsFromPromise) {
                        if (newOptions_1 === _this._currentPromise) {
                            _this.props.hoisted.setCurrentOptions(newOptionsFromPromise);
                        }
                    });
                }
            }
        };
        /**
         * OnBlur handler. Set the focused state to false
         * and submit any pending value
         */
        // eslint-disable-next-line deprecation/deprecation
        _this._onBlur = function (event) {
            var _a, _b;
            // Do nothing if the blur is coming from something
            // inside the comboBox root or the comboBox menu since
            // it we are not really blurring 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 = getActiveElement();
            }
            if (relatedTarget) {
                var isBlurFromComboBoxTitle = (_a = _this.props.hoisted.rootRef.current) === null || _a === void 0 ? void 0 : _a.contains(relatedTarget);
                var isBlurFromComboBoxMenu = (_b = _this._comboBoxMenu.current) === null || _b === void 0 ? void 0 : _b.contains(relatedTarget);
                var isBlurFromComboBoxMenuAncestor = _this._comboBoxMenu.current &&
                    findElementRecursive(_this._comboBoxMenu.current, function (element) { return element === relatedTarget; });
                if (isBlurFromComboBoxTitle || isBlurFromComboBoxMenu || isBlurFromComboBoxMenuAncestor) {
                    if (isBlurFromComboBoxMenuAncestor &&
                        _this._hasFocus() &&
                        (!_this.props.multiSelect || _this.props.allowFreeform)) {
                        _this._submitPendingValue(event);
                    }
                    event.preventDefault();
                    event.stopPropagation();
                    return;
                }
            }
            if (_this._hasFocus()) {
                _this.setState({ focusState: 'none' });
                if (!_this.props.multiSelect || _this.props.allowFreeform) {
                    _this._submitPendingValue(event);
                }
            }
        };
        // Render Callout container and pass in list
        _this._onRenderContainer = function (props, defaultRender) {
            var onRenderList = props.onRenderList, calloutProps = props.calloutProps, dropdownWidth = props.dropdownWidth, dropdownMaxWidth = props.dropdownMaxWidth, _a = props.onRenderUpperContent, onRenderUpperContent = _a === void 0 ? _this._onRenderUpperContent : _a, _b = props.onRenderLowerContent, onRenderLowerContent = _b === void 0 ? _this._onRenderLowerContent : _b, useComboBoxAsMenuWidth = props.useComboBoxAsMenuWidth, persistMenu = props.persistMenu, _c = props.shouldRestoreFocus, shouldRestoreFocus = _c === void 0 ? true : _c;
            var isOpen = _this.state.isOpen;
            var id = _this._id;
            var comboBoxMenuWidth = useComboBoxAsMenuWidth && _this._comboBoxWrapper.current
                ? _this._comboBoxWrapper.current.clientWidth + 2
                : undefined;
            return (React.createElement(Callout, __assign({ isBeakVisible: false, gapSpace: 0, doNotLayer: false, directionalHint: DirectionalHint.bottomLeftEdge, directionalHintFixed: false }, calloutProps, { onLayerMounted: _this._onLayerMounted, className: css(_this._classNames.callout, calloutProps === null || calloutProps === void 0 ? void 0 : calloutProps.className), target: _this._comboBoxWrapper.current, onDismiss: _this._onDismiss, onMouseDown: _this._onCalloutMouseDown, onScroll: _this._onScroll, setInitialFocus: false, calloutWidth: useComboBoxAsMenuWidth && _this._comboBoxWrapper.current
                    ? comboBoxMenuWidth && comboBoxMenuWidth
                    : dropdownWidth, calloutMaxWidth: dropdownMaxWidth ? dropdownMaxWidth : comboBoxMenuWidth, hidden: persistMenu ? !isOpen : undefined, shouldRestoreFocus: shouldRestoreFocus }),
                onRenderUpperContent(_this.props, _this._onRenderUpperContent),
                React.createElement("div", { className: _this._classNames.optionsContainerWrapper, ref: _this._comboBoxMenu }, onRenderList === null || onRenderList === void 0 ? void 0 : onRenderList(__assign(__assign({}, props), { id: id }), _this._onRenderList)),
                onRenderLowerContent(_this.props, _this._onRenderLowerContent)));
        };
        _this._onLayerMounted = function () {
            _this._onCalloutLayerMounted();
            // need to call this again here to get the correct scroll parent dimensions
            // when the callout is first opened
            _this._async.setTimeout(function () {
                _this._scrollIntoView();
            }, 0);
            if (_this.props.calloutProps && _this.props.calloutProps.onLayerMounted) {
                _this.props.calloutProps.onLayerMounted();
            }
        };
        _this._onRenderLabel = function (onRenderLabelProps) {
            var _a = onRenderLabelProps.props, label = _a.label, disabled = _a.disabled, required = _a.required;
            if (label) {
                return (React.createElement(Label, { id: _this._id + '-label', disabled: disabled, required: required, className: _this._classNames.label },
                    label,
                    onRenderLabelProps.multiselectAccessibleText && (React.createElement("span", { className: _this._classNames.screenReaderText }, onRenderLabelProps.multiselectAccessibleText))));
            }
            return null;
        };
        // Render List of items
        _this._onRenderList = function (props) {
            var _a = props.onRenderItem, onRenderItem = _a === void 0 ? _this._onRenderItem : _a, label = props.label, ariaLabel = props.ariaLabel, multiSelect = props.multiSelect;
            var queue = { items: [] };
            var renderedList = [];
            var emptyQueue = function () {
                var newGroup = queue.id
                    ? [
                        React.createElement("div", { role: "group", key: queue.id, "aria-labelledby": queue.id }, queue.items),
                    ]
                    : queue.items;
                renderedList = __spreadArray(__spreadArray([], renderedList), newGroup);
                // Flush items and id
                queue = { items: [] };
            };
            var placeRenderedOptionIntoQueue = function (item, index) {
                /*
                  Case Header
                    empty queue if it's not already empty
                    ensure unique ID for header and set queue ID
                    push header into queue
                  Case Divider
                    push divider into queue if not first item
                    empty queue if not already empty
                  Default
                    push item into queue
                */
                switch (item.itemType) {
                    case SelectableOptionMenuItemType.Header:
                        queue.items.length > 0 && emptyQueue();
                        var id_1 = _this._id + item.key;
                        queue.items.push(onRenderItem(__assign(__assign({ id: id_1 }, item), { index: index }), _this._onRenderItem));
                        queue.id = id_1;
                        break;
                    case SelectableOptionMenuItemType.Divider:
                        index > 0 && queue.items.push(onRenderItem(__assign(__assign({}, item), { index: index }), _this._onRenderItem));
                        queue.items.length > 0 && emptyQueue();
                        break;
                    default:
                        queue.items.push(onRenderItem(__assign(__assign({}, item), { index: index }), _this._onRenderItem));
                }
            };
            // Place options into the queue. Queue will be emptied anytime a Header or Divider is encountered
            props.options.forEach(function (item, index) {
                placeRenderedOptionIntoQueue(item, index);
            });
            // Push remaining items into all renderedList
            queue.items.length > 0 && emptyQueue();
            var id = _this._id;
            return (React.createElement("div", { id: id + '-list', className: _this._classNames.optionsContainer, "aria-labelledby": label && id + '-label', "aria-label": ariaLabel && !label ? ariaLabel : undefined, "aria-multiselectable": multiSelect ? 'true' : undefined, role: "listbox" }, renderedList));
        };
        // Render items
        _this._onRenderItem = function (item) {
            switch (item.itemType) {
                case SelectableOptionMenuItemType.Divider:
                    return _this._renderSeparator(item);
                case SelectableOptionMenuItemType.Header:
                    return _this._renderHeader(item);
                default:
                    return _this._renderOption(item);
            }
        };
        // Default _onRenderLowerContent function returns nothing
        _this._onRenderLowerContent = function () {
            return null;
        };
        // Default _onRenderUpperContent function returns nothing
        _this._onRenderUpperContent = 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 isChecked = _this._isOptionChecked(item.index);
            var isIndeterminate = _this._isOptionIndeterminate(item.index);
            var optionStyles = _this._getCurrentOptionStyles(item);
            var optionClassNames = getComboBoxOptionClassNames(_this._getCurrentOptionStyles(item));
            var title = item.title;
            var onRenderCheckboxLabel = function () { return onRenderOption(item, _this._onRenderOptionContent); };
            var getOptionComponent = function () {
                return !_this.props.multiSelect ? (React.createElement(CommandButton, { id: id + '-list' + item.index, key: item.key, "data-index": item.index, styles: optionStyles, checked: isSelected, className: 'ms-ComboBox-option', onClick: _this._onItemClick(item), 
                    // eslint-disable-next-line react/jsx-no-bind
                    onMouseEnter: _this._onOptionMouseEnter.bind(_this, item.index), 
                    // eslint-disable-next-line react/jsx-no-bind
                    onMouseMove: _this._onOptionMouseMove.bind(_this, item.index), onMouseLeave: _this._onOptionMouseLeave, role: "option", "aria-selected": isSelected ? 'true' : 'false', ariaLabel: item.ariaLabel, disabled: item.disabled, title: title }, React.createElement("span", { className: optionClassNames.optionTextWrapper, ref: isSelected ? _this._selectedElement : undefined }, onRenderOption(item, _this._onRenderOptionContent)))) : (React.createElement(Checkbox, { id: id + '-list' + item.index, ariaLabel: item.ariaLabel, key: item.key, styles: optionStyles, className: 'ms-ComboBox-option', onChange: _this._onItemClick(item), label: item.text, checked: isChecked, indeterminate: isIndeterminate, title: title, disabled: item.disabled, 
                    // eslint-disable-next-line react/jsx-no-bind
                    onRenderLabel: onRenderCheckboxLabel, inputProps: __assign({ 
                        // aria-selected should only be applied to checked items, not hovered items
                        'aria-selected': isChecked ? 'true' : 'false', role: 'option' }, {
                        'data-index': item.index,
                        'data-is-focusable': true,
                    }) }));
            };
            return (React.createElement(ComboBoxOptionWrapper, { key: item.key, index: item.index, disabled: item.disabled, isSelected: isSelected, isChecked: isChecked, isIndeterminate: isIndeterminate, text: item.text, 
                // eslint-disable-next-line react/jsx-no-bind
                render: getOptionComponent, data: item.data }));
        };
        /**
         * Mouse clicks to headers, dividers and scrollbar should not make input lose focus
         */
        _this._onCalloutMouseDown = function (ev) {
            ev.preventDefault();
        };
        /**
         * Scroll handler for the callout to make sure the mouse events
         * for updating focus are not interacting during scroll
         */
        _this._onScroll = function () {
            var _a;
            if (!_this._isScrollIdle && _this._scrollIdleTimeoutId !== undefined) {
                _this._async.clearTimeout(_this._scrollIdleTimeoutId);
                _this._scrollIdleTimeoutId = undefined;
            }
            else {
                _this._isScrollIdle = false;
            }
            if ((_a = _this.props.calloutProps) === null || _a === void 0 ? void 0 : _a.onScroll) {
                _this.props.calloutProps.onScroll();
            }
            _this._scrollIdleTimeoutId = _this._async.setTimeout(function () {
                _this._isScrollIdle = true;
            }, ScrollIdleDelay);
        };
        _this._onRenderOptionContent = function (item) {
            var optionClassNames = 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, currentOptions = _a.hoisted.currentOptions;
            var _b = _this.state, isOpen = _b.isOpen, currentPendingValueValidIndexOnHover = _b.currentPendingValueValidIndexOnHover;
            // Take note if we are processing an alt (option) or meta (command) keydown.
            // See comment in _onInputKeyUp for reasoning.
            _this._lastKeyDownWasAltOrMeta = isAltOrMeta(ev);
            if (disabled) {
                _this._handleInputWhenDisabled(ev);
                return;
            }
            var index = _this._getPendingSelectedIndex(false /* includeCurrentPendingValue */);
            // eslint-disable-next-line deprecation/deprecation
            switch (ev.which) {
                case 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 KeyCodes.tab:
                    // On enter submit the pending value
                    if (!_this.props.multiSelect) {
                        _this._submitPendingValue(ev);
                    }
                    // If we are not allowing freeform
                    // or the combo box is open, flip the open state
                    if (isOpen) {
                        _this._setOpenStateAndFocusOnClose(!isOpen, false /* focusInputAfterClose */);
                    }
                    // Allow TAB to propagate
                    return;
                case KeyCodes.escape:
                    // reset the selected index
                    _this._resetSelectedIndex();
                    // Close the menu if opened
                    if (isOpen) {
                        _this.setState({
                            isOpen: false,
                        });
                    }
                    else {
                        return;
                    }
                    break;
                case 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.props.hoisted.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;
                    }
                    // do not scroll page
                    ev.preventDefault();
                    // Go to the previous option
                    _this._setPendingInfoFromIndexAndDirection(index, SearchDirection.backward);
                    break;
                case KeyCodes.down:
                    // Expand the combo box 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;
                        }
                        // do not scroll page
                        ev.preventDefault();
                        // Got to the next option
                        _this._setPendingInfoFromIndexAndDirection(index, SearchDirection.forward);
                    }
                    break;
                case KeyCodes.home:
                case 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
                    // eslint-disable-next-line deprecation/deprecation
                    if (ev.which === KeyCodes.end) {
                        index = currentOptions.length;
                        directionToSearch = SearchDirection.backward;
                    }
                    _this._setPendingInfoFromIndexAndDirection(index, directionToSearch);
                    break;
                /* eslint-disable no-fallthrough */
                case KeyCodes.space:
                    // event handled in _onComboBoxKeyUp
                    if (!allowFreeform && autoComplete === 'off') {
                        break;
                    }
                default:
                    /* eslint-enable no-fallthrough */
                    // are we processing a function key? if so bail out
                    // eslint-disable-next-line deprecation/deprecation
                    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
                    // eslint-disable-next-line deprecation/deprecation
                    if (ev.keyCode === 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(ev.key);
                        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 && isAltOrMeta(ev);
            _this._lastKeyDownWasAltOrMeta = false;
            var shouldHandleKey = keyPressIsAltOrMetaAlone && !(isMac() || isIOS());
            if (disabled) {
                _this._handleInputWhenDisabled(ev);
                return;
            }
            // eslint-disable-next-line deprecation/deprecation
            switch (ev.which) {
                case KeyCodes.space:
                    // If we are not allowing freeform and are not autoComplete
                    // make space expand/collapse the combo box
                    // and allow the event to propagate
                    if (!allowFreeform && autoComplete === 'off') {
                        _this._setOpenStateAndFocusOnClose(!isOpen, !!isOpen);
                    }
                    return;
                default:
                    if (shouldHandleKey && isOpen) {
                        _this._setOpenStateAndFocusOnClose(!isOpen, true /* focusInputAfterClose */);
                    }
                    else {
                        if (_this.state.focusState === 'focusing' && _this.props.openOnKeyboardFocus) {
                            _this.setState({ isOpen: true });
                        }
                        if (_this.state.focusState !== 'focused') {
                            _this.setState({ focusState: 'focused' });
                        }
                    }
                    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 combo box and the input when not allowing freeform.
         * This toggles the expand/collapse state of the combo box (if enabled).
         */
        _this._onComboBoxClick = function () {
            var disabled = _this.props.disabled;
            var isOpen = _this.state.isOpen;
            if (!disabled) {
                _this._setOpenStateAndFocusOnClose(!isOpen, false /* focusInputAfterClose */);
                _this.setState({ focusState: 'focused' });
            }
        };
        /**
         * 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();
            }
        };
        initializeComponentRef(_this);
        _this._async = new Async(_this);
        _this._events = new EventGroup(_this);
        warnMutuallyExclusive(COMPONENT_NAME, props, {
            defaultSelectedKey: 'selectedKey',
            text: 'defaultSelectedKey',
            selectedKey: 'value',
            dropdownWidth: 'useComboBoxAsMenuWidth',
            ariaLabel: 'label',
        });
        _this._id = props.id || getId('ComboBox');
        _this._isScrollIdle = true;
        _this._processingTouch = false;
        _this._gotMouseMove = false;
        _this._processingClearPendingInfo = false;
        _this.state = {
            isOpen: false,
            focusState: 'none',
            currentPendingValueValidIndex: -1,
            currentPendingValue: undefined,
            currentPendingValueValidIndexOnHover: HoverStatus.default,
        };
        return _this;
    }
    Object.defineProperty(ComboBoxInternal.prototype, "selectedOptions", {
        /**
         * All selected options
         */
        get: function () {
            var _a = this.props.hoisted, currentOptions = _a.currentOptions, selectedIndices = _a.selectedIndices;
            return getAllSelectedOptions(currentOptions, selectedIndices);
        },
        enumerable: false,
        configurable: true
    });
    ComboBoxInternal.prototype.componentDidMount = function () {
        if (this._comboBoxWrapper.current && !this.props.disabled) {
            // 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);
            }
        }
    };
    ComboBoxInternal.prototype.componentDidUpdate = function (prevProps, prevState) {
        var _this = this;
        var _a = this.props, allowFreeform = _a.allowFreeform, text = _a.text, onMenuOpen = _a.onMenuOpen, onMenuDismissed = _a.onMenuDismissed, selectedIndices = _a.hoisted.selectedIndices;
        var _b = this.state, isOpen = _b.isOpen, 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 (this._hasFocus() &&
            (isOpen ||
                (prevState.isOpen &&
                    !isOpen &&
                    this._focusInputAfterClose &&
                    this._autofill.current &&
                    getActiveElement(getRootNode(this._autofill.current.inputElement)) !== 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) ||
                (this._hasFocus() &&
                    ((!isOpen &&
                        !this.props.multiSelect &&
                        prevProps.hoisted.selectedIndices &&
                        selectedIndices &&
                        prevProps.hoisted.selectedIndices[0] !== selectedIndices[0]) ||
                        !allowFreeform ||
                        text !== prevProps.text)))) {
            this._onFocus();
        }
        this._notifyPendingValueChanged(prevState);
        if (isOpen && !prevState.isOpen && onMenuOpen) {
            onMenuOpen();
        }
        if (!isOpen && prevState.isOpen && onMenuDismissed) {
            onMenuDismissed();
        }
    };
    ComboBoxInternal.prototype.componentWillUnmount = function () {
        this._async.dispose();
        this._events.dispose();
    };
    // Primary Render
    ComboBoxInternal.prototype.render = function () {
        var id = this._id;
        var errorMessageId = id + '-error';
        var _a = this.props, className = _a.className, disabled = _a.disabled, required = _a.required, errorMessage = _a.errorMessage, _b = _a.onRenderContainer, onRenderContainer = _b === void 0 ? this._onRenderContainer : _b, _c = _a.onRenderLabel, onRenderLabel = _c === void 0 ? this._onRenderLabel : _c, _d = _a.onRenderList, onRenderList = _d === void 0 ? this._onRenderList : _d, _e = _a.onRenderItem, onRenderItem = _e === void 0 ? this._onRenderItem : _e, _f = _a.onRenderOption, onRenderOption = _f === void 0 ? this._onRenderOptionContent : _f, allowFreeform = _a.allowFreeform, customStyles = _a.styles, theme = _a.theme, persistMenu = _a.persistMenu, multiSelect = _a.multiSelect, _g = _a.hoisted, suggestedDisplayValue = _g.suggestedDisplayValue, selectedIndices = _g.selectedIndices, currentOptions = _g.currentOptions;
        var isOpen = this.state.isOpen;
        this._currentVisibleValue = this._getVisibleValue();
        // Single select is already accessible since the whole text is selected
        // when focus enters the input. Since multiselect appears to clear the input
        // it needs special accessible text
        var multiselectAccessibleText = multiSelect
            ? this._getMultiselectDisplayString(selectedIndices, currentOptions, suggestedDisplayValue)
            : undefined;
        var divProps = getNativeProps(this.props, divProperties, [
            'onChange',
            'value',
            'aria-describedby',
            'aria-labelledby',
        ]);
        var hasErrorMessage = errorMessage && errorMessage.length > 0 ? true : false;
        this._classNames = this.props.getClassNames
            ? this.props.getClassNames(theme, !!isOpen, !!disabled, !!required, !!this._hasFocus(), !!allowFreeform, !!hasErrorMessage, className)
            : getClassNames(getStyles(theme, customStyles), className, !!isOpen, !!disabled, !!required, !!this._hasFocus(), !!allowFreeform, !!hasErrorMessage);
        var comboBoxWrapper = this._renderComboBoxWrapper(multiselectAccessibleText, errorMessageId);
        return (React.createElement("div", __assign({}, divProps, { ref: this.props.hoisted.mergedRootRef, className: this._classNames.container }),
            onRenderLabel({ props: this.props, multiselectAccessibleText: multiselectAccessibleText }, this._onRenderLabel),
            comboBoxWrapper,
            (persistMenu || isOpen) &&
                onRenderContainer(__assign(__assign({}, this.props), { onRenderList: onRenderList, onRenderItem: onRenderItem, onRenderOption: onRenderOption, options: currentOptions.map(function (item, index) { return (__assign(__assign({}, item), { index: index })); }), onDismiss: this._onDismiss }), this._onRenderContainer),
            hasErrorMessage && (React.createElement("div", { role: "alert", id: errorMessageId, className: this._classNames.errorMessage }, errorMessage))));
    };
    ComboBoxInternal.prototype._getPendingString = function (currentPendingValue, currentOptions, index) {
        return currentPendingValue !== null && currentPendingValue !== undefined
            ? currentPendingValue
            : indexWithinBounds(currentOptions, index)
                ? getPreviewText(currentOptions[index])
                : '';
    };
    /**
     * Returns a string that concatenates all of the selected values
     * for multiselect combo box.
     */
    ComboBoxInternal.prototype._getMultiselectDisplayString = function (selectedIndices, currentOptions, suggestedDisplayValue) {
        var displayValues = [];
        for (var idx = 0; selectedIndices && idx < selectedIndices.length; idx++) {
            var index = selectedIndices[idx];
            if (currentOptions[index].itemType !== SelectableOptionMenuItemType.SelectAll) {
                displayValues.push(indexWithinBounds(currentOptions, index)
                    ? currentOptions[index].text
                    : normalizeToString(suggestedDisplayValue));
            }
        }
        var _a = this.props.multiSelectDelimiter, multiSelectDelimiter = _a === void 0 ? ', ' : _a;
        return displayValues.join(multiSelectDelimiter);
    };
    /**
     * Process the new input's new value when the combo box allows freeform entry
     * @param updatedValue - the input's newly changed value
     */
    ComboBoxInternal.prototype._processInputChangeWithFreeform = function (updatedValue) {
        var currentOptions = this.props.hoisted.currentOptions;
        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 (__assign(__assign({}, item), { index: index })); })
                .filter(function (option) { return isNormalOption(option) && 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 (__assign(__assign({}, item), { index: index })); })
                .filter(function (option) { return isNormalOption(option) && getPreviewText(option).toLocaleLowerCase().indexOf(updatedValue) === 0; });
            if (items.length > 0) {
                // use ariaLabel as the value when the option is set
                var text = 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 (__assign(__assign({}, item), { index: index })); })
                .filter(function (option) { return isNormalOption(option) && 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 combo box does not allow freeform entry
     * @param updatedValue - the input's newly changed value
     */
    ComboBoxInternal.prototype._processInputChangeWithoutFreeform = function (updatedValue) {
        var _this = this;
        var currentOptions = this.props.hoisted.currentOptions;
        var _a = this.state, currentPendingValue = _a.currentPendingValue, currentPendingValueValidIndex = _a.currentPendingValueValidIndex;
        if (this.props.autoComplete === 'on') {
            // If autoComplete is on while allow freeform is off,
            // we will remember the key press 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 key press happening
                // within the timeout of each other so remove the clearing task
                // and continue building the pending value with the updated value
                if (this._autoCompleteTimeout) {
                    this._async.clearTimeout(this._autoCompleteTimeout);
                    this._autoCompleteTimeout = undefined;
                    updatedValue = 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 (__assign(__assign({}, item), { index: i })); })
                    .filter(function (option) { return isNormalOption(option) && option.text.toLocaleLowerCase().indexOf(updatedValue) === 0; });
                // If we found a match, update the state
                if (items.length > 0) {
                    this._setPendingInfo(originalUpdatedValue, items[0].index, getPreviewText(items[0]));
                }
                // Schedule a timeout to clear the pending value after the timeout span
                this._autoCompleteTimeout = this._async.setTimeout(function () {
                    _this._autoCompleteTimeout = 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);
    };
    ComboBoxInternal.prototype._getFirstSelectedIndex = function () {
        var selectedIndices = this.props.hoisted.selectedIndices;
        return (selectedIndices === null || selectedIndices === void 0 ? void 0 : selectedIndices.length) ? 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
     */
    ComboBoxInternal.prototype._getNextSelectableIndex = function (index, searchDirection) {
        var currentOptions = this.props.hoisted.currentOptions;
        var newIndex = index + searchDirection;
        newIndex = Math.max(0, Math.min(currentOptions.length - 1, newIndex));
        if (!indexWithinBounds(currentOptions, newIndex)) {
            return -1;
        }
        var option = currentOptions[newIndex];
        if (!isSelectableOption(option) || 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
     */
    ComboBoxInternal.prototype._setSelectedIndex = function (index, submitPendingValueEvent, searchDirection) {
        if (searchDirection === void 0) { searchDirection = SearchDirection.none; }
        var _a = this.props, onChange = _a.onChange, onPendingValueChanged = _a.onPendingValueChanged, _b = _a.hoisted, initialIndices = _b.selectedIndices, currentOptions = _b.currentOptions;
        // Clone currentOptions and selectedIndices so we don't mutate state
        var selectedIndices = initialIndices ? initialIndices.slice() : [];
        var changedOptions = currentOptions.slice();
        // Find the next selectable index, if searchDirection is none
        // we will get our starting index back
        index = this._getNextSelectableIndex(index, searchDirection);
        if (!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 = __assign({}, currentOptions[index]);
            // if option doesn't existing, or option is disabled, we noop
            if (!option || option.disabled) {
                return;
            }
            if (this.props.multiSelect) {
                // Setting the initial state of option.selected in Multi-select combo box by checking the
                // selectedIndices array and overriding the undefined issue
                option.selected = option.selected !== undefined ? !option.selected : selectedIndices.indexOf(index) < 0;
                // handle changing all options if SelectAll is changed
                if (option.itemType === SelectableOptionMenuItemType.SelectAll) {
                    selectedIndices = [];
                    // if select all is set to checked, push all selectable option indices
                    if (option.selected) {
                        currentOptions.forEach(function (currentOption, i) {
                            if (!currentOption.disabled && isSelectableOption(currentOption)) {
                                selectedIndices.push(i);
                                changedOptions[i] = __assign(__assign({}, currentOption), { selected: true });
                            }
                        });
                    }
                    // otherwise un-check all options
                    else {
                        changedOptions = currentOptions.map(function (currentOption) { return (__assign(__assign({}, currentOption), { selected: false })); });
                    }
                }
                // otherwise update the individual option
                else {
                    if (option.selected && selectedIndices.indexOf(index) < 0) {
                        selectedIndices.push(index);
                    }
                    else if (!option.selected && selectedIndices.indexOf(index) >= 0) {
                        selectedIndices = selectedIndices.filter(function (value) { return value !== index; });
                    }
                    changedOptions[index] = option;
                    // If SelectAll exists and another option was toggled, update the SelectAll option's state
                    var selectAllOption = changedOptions.filter(function (o) { return o.itemType === SelectableOptionMenuItemType.SelectAll; })[0];
                    if (selectAllOption) {
                        var selectAllState = this._isSelectAllChecked(selectedIndices);
                        var selectAllIndex_1 = changedOptions.indexOf(selectAllOption);
                        if (selectAllState) {
                            selectedIndices.push(selectAllIndex_1);
                            changedOptions[selectAllIndex_1] = __assign(__assign({}, selectAllOption), { selected: true });
                        }
                        else {
                            selectedIndices = selectedIndices.filter(function (value) { return value !== selectAllIndex_1; });
                            changedOptions[selectAllIndex_1] = __assign(__assign({}, selectAllOption), { selected: false });
                        }
                    }
                }
            }
            else {
                selectedIndices[0] = index;
            }
            submitPendingValueEvent.persist();
            // Only setState if combo box is uncontrolled.
            if (this.props.selectedKey || this.props.selectedKey === null) {
                // If combo box value is changed, revert preview first
                if (this._hasPendingValue && onPendingValueChanged) {
                    onPendingValueChanged();
                    this._hasPendingValue = false;
                }
            }
            else {
                this.props.hoisted.setSelectedIndices(selectedIndices);
                this.props.hoisted.setCurrentOptions(changedOptions);
                // If ComboBox value is changed, revert preview first
                if (this._hasPendingValue && onPendingValueChanged) {
                    onPendingValueChanged();
                    this._hasPendingValue = false;
                }
            }
            // Call onChange after state is updated
            if (onChange) {
                onChange(submitPendingValueEvent, option, index, getPreviewText(option));
            }
        }
        if (this.props.multiSelect && this.state.isOpen) {
            return;
        }
        // clear all of the pending info
        this._clearPendingInfo();
    };
    /**
     * Submit a pending value if there is one
     */
    ComboBoxInternal.prototype._submitPendingValue = function (submitPendingValueEvent) {
        var _a;
        var _b = this.props, onChange = _b.onChange, allowFreeform = _b.allowFreeform, autoComplete = _b.autoComplete, multiSelect = _b.multiSelect, hoisted = _b.hoisted;
        var currentOptions = hoisted.currentOptions;
        var _c = this.state, currentPendingValue = _c.currentPendingValue, currentPendingValueValidIndex = _c.currentPendingValueValidIndex, currentPendingValueValidIndexOnHover = _c.currentPendingValueValidIndexOnHover;
        var selectedIndices = this.props.hoisted.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 (indexWithinBounds(currentOptions, currentPendingValueValidIndex)) {
                var pendingOptionText = getPreviewText(currentOptions[currentPendingValueValidIndex]).toLocaleLowerCase();
                var autofill = this._autofill.current;
                // By exact match, that means: our pending value is the same as 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 &&
                        (autofill === null || autofill === void 0 ? void 0 : autofill.isValueSelected) &&
                        currentPendingValue.length + (autofill.selectionEnd - autofill.selectionStart) ===
                            pendingOptionText.length) ||
                    ((_a = autofill === null || autofill === void 0 ? void 0 : autofill.inputElement) === null || _a === void 0 ? void 0 : _a.value.toLocaleLowerCase()) === pendingOptionText) {
                    this._setSelectedIndex(currentPendingValueValidIndex, submitPendingValueEvent);
                    if (multiSelect && this.state.isOpen) {
                        return;
                    }
                    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 || getId(),
                    text: normalizeToString(currentPendingValue),
                };
                // If it's multiselect, set selected state to true
                if (multiSelect) {
                    newOption.selected = true;
                }
                var newOptions = currentOptions.concat([newOption]);
                if (selectedIndices) {
                    if (!multiSelect) {
                        selectedIndices = [];
                    }
                    selectedIndices.push(newOptions.length - 1);
                }
                hoisted.setCurrentOptions(newOptions);
                hoisted.setSelectedIndices(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();
    };
    ComboBoxInternal.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
    ComboBoxInternal.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;
    };
    ComboBoxInternal.prototype._renderHeader = function (item) {
        var _a = this.props.onRenderOption, onRenderOption = _a === void 0 ? this._onRenderOptionContent : _a;
        return (React.createElement("div", { id: item.id, 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
     * */
    ComboBoxInternal.prototype._isOptionHighlighted = 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;
        }
        return currentPendingValueValidIndexOnHover >= 0
            ? currentPendingValueValidIndexOnHover === index
            : this._isOptionSelected(index);
    };
    ComboBoxInternal.prototype._isOptionSelected = function (index) {
        return this._getPendingSelectedIndex(true /* includePendingValue */) === index;
    };
    ComboBoxInternal.prototype._isOptionChecked = function (index) {
        if (this.props.multiSelect && index !== undefined && this.props.hoisted.selectedIndices) {
            var idxOfSelectedIndex = -1;
            idxOfSelectedIndex = this.props.hoisted.selectedIndices.indexOf(index);
            return idxOfSelectedIndex >= 0;
        }
        return false;
    };
    ComboBoxInternal.prototype._isOptionIndeterminate = function (index) {
        var _a = this.props, multiSelect = _a.multiSelect, hoisted = _a.hoisted;
        if (multiSelect && index !== undefined && hoisted.selectedIndices && hoisted.currentOptions) {
            var option = hoisted.currentOptions[index];
            if (option && option.itemType === SelectableOptionMenuItemType.SelectAll) {
                return hoisted.selectedIndices.length > 0 && !this._isSelectAllChecked();
            }
        }
        return false;
    };
    ComboBoxInternal.prototype._isSelectAllChecked = function (testIndices) {
        var _a = this.props, multiSelect = _a.multiSelect, hoisted = _a.hoisted;
        var selectAllOption = hoisted.currentOptions.find(function (option) { return option.itemType === SelectableOptionMenuItemType.SelectAll; });
        var selectedIndices = testIndices || hoisted.selectedIndices;
        if (!multiSelect || !selectedIndices || !selectAllOption) {
            return false;
        }
        // start by not including the select all option itself
        var selectAllIndex = hoisted.currentOptions.indexOf(selectAllOption);
        var compareSelectedIndices = selectedIndices.filter(function (value) { return value !== selectAllIndex; });
        // get array of selectable options, excluding disabled options, headers, and dividers
        var selectableOptions = hoisted.currentOptions.filter(function (option) {
            return !option.disabled && option.itemType !== SelectableOptionMenuItemType.SelectAll && isSelectableOption(option);
        });
        return compareSelectedIndices.length === selectableOptions.length;
    };
    /**
     * Gets the pending selected index taking into account valueValidIndex and selectedIndex
     * @param includeCurrentPendingValue - Should we include the currentPendingValue when
     * finding the index
     */
    ComboBoxInternal.prototype._getPendingSelectedIndex = function (includeCurrentPendingValue) {
        var _a = this.state, currentPendingValueValidIndex = _a.currentPendingValueValidIndex, currentPendingValue = _a.currentPendingValue;
        return currentPendingValueValidIndex >= 0 ||
            (includeCurrentPendingValue && currentPendingValue !== null && currentPendingValue !== undefined)
            ? currentPendingValueValidIndex
            : this.props.multiSelect
                ? 0
                : this._getFirstSelectedIndex();
    };
    /**
     * Scroll the selected element into view
     */
    ComboBoxInternal.prototype._scrollIntoView = function () {
        var _a = this.props, onScrollToItem = _a.onScrollToItem, scrollSelectedToTop = _a.scrollSelectedToTop;
        var currentPendingSelectedIndex = this._getPendingSelectedIndex(true);
        if (onScrollToItem) {
            // Use the custom scroll handler
            onScrollToItem(currentPendingSelectedIndex >= 0 ? currentPendingSelectedIndex : this._getFirstSelectedIndex());
            return;
        }
        var scrollToElement = this._selectedElement.current;
        // in multi-select there are multiple selected elements, so we use the pending select index
        // to locate the option to scroll to.
        if (this.props.multiSelect && this._comboBoxMenu.current) {
            scrollToElement = findFirstDescendant(this._comboBoxMenu.current, function (element) {
                var _a;
                return ((_a = element.dataset) === null || _a === void 0 ? void 0 : _a.index) === currentPendingSelectedIndex.toString();
            });
        }
        if (scrollToElement && scrollToElement.offsetParent) {
            var alignToTop = true;
            // We are using refs, scroll the ref into view
            if (this._comboBoxMenu.current && this._comboBoxMenu.current.offsetParent) {
                var scrollableParent = this._comboBoxMenu.current.offsetParent;
                var selectedElement = scrollToElement.offsetParent;
                var _b = selectedElement, offsetHeight = _b.offsetHeight, offsetTop = _b.offsetTop;
                var _c = scrollableParent, parentOffsetHeight = _c.offsetHeight, scrollTop = _c.scrollTop;
                var isAbove = offsetTop < scrollTop;
                var isBelow = offsetTop + offsetHeight > scrollTop + parentOffsetHeight;
                if (isAbove || scrollSelectedToTop) {
                    alignToTop = false;
                    scrollableParent.scrollTo(0, offsetTop);
                }
                else if (isBelow) {
                    scrollableParent.scrollTo(0, offsetTop - parentOffsetHeight + offsetHeight);
                }
            }
            // if _comboboxMenu doesn't exist, fall back to scrollIntoView
            else {
                scrollToElement.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
     */
    ComboBoxInternal.prototype._onItemClick = function (item) {
        var _this = this;
        var onItemClick = this.props.onItemClick;
        var index = item.index;
        return function (ev) {
            // only close the callout when it's in single-select mode
            if (!_this.props.multiSelect) {
                // ensure that focus returns to the input, not the button
                _this._autofill.current && _this._autofill.current.focus();
                _this.setState({
                    isOpen: false,
                });
            }
            // Continue processing the click only after
            // performing menu close / control focus(inner working)
            onItemClick && onItemClick(ev, item, index);
            _this._setSelectedIndex(index, ev);
        };
    };
    /**
     * 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
     */
    ComboBoxInternal.prototype._resetSelectedIndex = function () {
        var currentOptions = this.props.hoisted.currentOptions;
        this._clearPendingInfo();
        var selectedIndex = this._getFirstSelectedIndex();
        if (selectedIndex > 0 && selectedIndex < currentOptions.length) {
            this.props.hoisted.setSuggestedDisplayValue(currentOptions[selectedIndex].text);
        }
        else if (this.props.text) {
            // If we had a value initially, restore it
            this.props.hoisted.setSuggestedDisplayValue(this.props.text);
        }
    };
    /**
     * Clears the pending info state
     */
    ComboBoxInternal.prototype._clearPendingInfo = function () {
        this._processingClearPendingInfo = true;
        this.props.hoisted.setSuggestedDisplayValue(undefined);
        this.setState({
            currentPendingValue: undefined,
            currentPendingValueValidIndex: -1,
            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
     */
    ComboBoxInternal.prototype._setPendingInfo = function (currentPendingValue, currentPendingValueValidIndex, suggestedDisplayValue) {
        if (currentPendingValueValidIndex === void 0) { currentPendingValueValidIndex = -1; }
        if (this._processingClearPendingInfo) {
            return;
        }
        this.props.hoisted.setSuggestedDisplayValue(suggestedDisplayValue);
        this.setState({
            currentPendingValue: normalizeToString(currentPendingValue),
            currentPendingValueValidIndex: currentPendingValueValidIndex,
            currentPendingValueValidIndexOnHover: HoverStatus.default,
        });
    };
    /**
     * Set the pending info from the given index
     * @param index - the index to set the pending info from
     */
    ComboBoxInternal.prototype._setPendingInfoFromIndex = function (index) {
        var currentOptions = this.props.hoisted.currentOptions;
        if (index >= 0 && index < currentOptions.length) {
            var option = currentOptions[index];
            this._setPendingInfo(getPreviewText(option), index, getPreviewText(option));
        }
        else {
            this._clearPendingInfo();
        }
    };
    /**
     * Sets the pending info for the combo box
     * @param index - the index to search from
     * @param searchDirection - the direction to search
     */
    ComboBoxInternal.prototype._setPendingInfoFromIndexAndDirection = function (index, searchDirection) {
        var currentOptions = this.props.hoisted.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 indices 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 (indexWithinBounds(currentOptions, index)) {
            this._setPendingInfoFromIndex(index);
        }
    };
    ComboBoxInternal.prototype._notifyPendingValueChanged = function (prevState) {
        var onPendingValueChanged = this.props.onPendingValueChanged;
        if (!onPendingValueChanged) {
            return;
        }
        var currentOptions = this.props.hoisted.currentOptions;
        var _a = this.state, currentPendingValue = _a.currentPendingValue, currentPendingValueValidIndex = _a.currentPendingValueValidIndex, currentPendingValueValidIndexOnHover = _a.currentPendingValueValidIndexOnHover;
        var newPendingIndex = undefined;
        var newPendingValue = undefined;
        if (currentPendingValueValidIndexOnHover !== prevState.currentPendingValueValidIndexOnHover &&
            indexWithinBounds(currentOptions, currentPendingValueValidIndexOnHover)) {
            // Set new pending index if hover index was changed
            newPendingIndex = currentPendingValueValidIndexOnHover;
        }
        else if (currentPendingValueValidIndex !== prevState.currentPendingValueValidIndex &&
            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
     */
    ComboBoxInternal.prototype._setOpenStateAndFocusOnClose = function (isOpen, focusInputAfterClose) {
        this._focusInputAfterClose = focusInputAfterClose;
        this.setState({
            isOpen: isOpen,
        });
    };
    ComboBoxInternal.prototype._onOptionMouseEnter = function (index) {
        if (this._shouldIgnoreMouseEvent()) {
            return;
        }
        this.setState({
            currentPendingValueValidIndexOnHover: index,
        });
    };
    ComboBoxInternal.prototype._onOptionMouseMove = function (index) {
        this._gotMouseMove = true;
        if (!this._isScrollIdle || this.state.currentPendingValueValidIndexOnHover === index) {
            return;
        }
        this.setState({
            currentPendingValueValidIndexOnHover: index,
        });
    };
    ComboBoxInternal.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
     */
    ComboBoxInternal.prototype._handleInputWhenDisabled = function (ev) {
        // If we are disabled, close the menu (if needed)
        // and eat all keystrokes 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 &&
                // eslint-disable-next-line deprecation/deprecation
                ev.which !== KeyCodes.tab &&
                // eslint-disable-next-line deprecation/deprecation
                ev.which !== KeyCodes.escape &&
                // eslint-disable-next-line deprecation/deprecation
                (ev.which < 112 /* F1 */ || ev.which > 123) /* F12 */) {
                ev.stopPropagation();
                ev.preventDefault();
            }
        }
    };
    ComboBoxInternal.prototype._handleTouchAndPointerEvent = function () {
        var _this = this;
        // If we already have an existing timeout 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
     */
    ComboBoxInternal.prototype._getCaretButtonStyles = function () {
        var customCaretDownButtonStyles = this.props.caretDownButtonStyles;
        return getCaretDownButtonStyles(this.props.theme, customCaretDownButtonStyles);
    };
    /**
     * Get the styles for the current option.
     * @param item - Item props for the current option
     */
    ComboBoxInternal.prototype._getCurrentOptionStyles = function (item) {
        var customStylesForAllOptions = this.props.comboBoxOptionStyles;
        var customStylesForCurrentOption = item.styles;
        return getOptionStyles(this.props.theme, customStylesForAllOptions, customStylesForCurrentOption, this._isPendingOption(item), item.hidden, this._isOptionHighlighted(item.index));
    };
    /**
     * Get the aria-activedescendant value for the combo box.
     * @returns the id of the current focused combo item, otherwise the id of the currently selected element,
     * null otherwise
     */
    ComboBoxInternal.prototype._getAriaActiveDescendantValue = function () {
        var selectedIndices = this.props.hoisted.selectedIndices;
        var _a = this.state, isOpen = _a.isOpen, currentPendingValueValidIndex = _a.currentPendingValueValidIndex;
        var descendantText = isOpen && (selectedIndices === null || selectedIndices === void 0 ? void 0 : selectedIndices.length) ? this._id + '-list' + selectedIndices[0] : undefined;
        if (isOpen && this._hasFocus() && currentPendingValueValidIndex !== -1) {
            descendantText = this._id + '-list' + currentPendingValueValidIndex;
        }
        return descendantText;
    };
    /**
     * Get the aria autocomplete value for the combo box
     * @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 'list' if auto-complete is not enabled as selection is the only option.
     * Ideally, this should be 'none' if auto-complete is not enabled, but there is a known bug in Edge
     * where the callout may appear over the combo box if this attribute is set to 'none'
     */
    ComboBoxInternal.prototype._getAriaAutoCompleteValue = function () {
        var autoComplete = !this.props.disabled && this.props.autoComplete === 'on';
        return autoComplete ? (this.props.allowFreeform ? 'inline' : 'both') : 'list';
    };
    ComboBoxInternal.prototype._isPendingOption = function (item) {
        return item && item.index === this.state.currentPendingValueValidIndex;
    };
    /**
     * Returns true if the component has some kind of focus. If it's either focusing or if it's focused
     */
    ComboBoxInternal.prototype._hasFocus = function () {
        return this.state.focusState !== 'none';
    };
    ComboBoxInternal = __decorate([
        customizable('ComboBox', ['theme', 'styles'], true)
    ], ComboBoxInternal);
    return ComboBoxInternal;
}(React.Component));
/**
 * Get the indices of the options that are marked as selected
 * @param options - the combo box 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
 */
function getSelectedIndices(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 = 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();
}
/**
 * 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 primitive type.  This normalization makes caller's logic easier.
 */
function buildDefaultSelectedKeys(defaultSelectedKey, selectedKey) {
    var selectedKeys = buildSelectedKeys(defaultSelectedKey);
    if (selectedKeys.length) {
        return selectedKeys;
    }
    return buildSelectedKeys(selectedKey);
}
function buildSelectedKeys(selectedKey) {
    if (selectedKey === undefined) {
        return [];
    }
    // need to cast here so typescript does not complain
    return (selectedKey instanceof Array ? selectedKey : [selectedKey]);
}
function normalizeToString(value) {
    return value || '';
}
/**
 * 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
 */
function indexWithinBounds(options, index) {
    return !!options && index >= 0 && index < options.length;
}
/** Whether this is a normal option, not a header or divider or select all. */
function isNormalOption(option) {
    return (option.itemType !== SelectableOptionMenuItemType.Header &&
        option.itemType !== SelectableOptionMenuItemType.Divider &&
        option.itemType !== SelectableOptionMenuItemType.SelectAll);
}
/** Whether this is a selectable option, not a header or divider. */
function isSelectableOption(option) {
    return (option.itemType !== SelectableOptionMenuItemType.Header && option.itemType !== SelectableOptionMenuItemType.Divider);
}
/**
 * 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.
 */
function getPreviewText(item) {
    return item.useAriaLabelAsText && item.ariaLabel ? item.ariaLabel : item.text;
}
/**
 * Returns true if the key for the event is alt (Mac option) or meta (Mac command).
 */
function isAltOrMeta(ev) {
    // eslint-disable-next-line deprecation/deprecation
    return ev.which === KeyCodes.alt || ev.key === 'Meta';
}
//# sourceMappingURL=ComboBox.js.map