office-ui-fabric-react
Version: 
Reusable React components for building experiences for Office 365.
372 lines (370 loc) • 18.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var React = require("react");
var Dropdown_Props_1 = require("./Dropdown.Props");
var DirectionalHint_1 = require("../../common/DirectionalHint");
var Callout_1 = require("../../Callout");
var Label_1 = require("../../Label");
var Button_1 = require("../../Button");
var Panel_1 = require("../../Panel");
var Icon_1 = require("../../Icon");
var FocusZone_1 = require("../../FocusZone");
var withResponsiveMode_1 = require("../../utilities/decorators/withResponsiveMode");
var Utilities_1 = require("../../Utilities");
var SelectableOption_Props_1 = require("../../utilities/selectableOption/SelectableOption.Props");
var stylesImport = require("./Dropdown.scss");
var styles = stylesImport;
var Dropdown = Dropdown_1 = (function (_super) {
    tslib_1.__extends(Dropdown, _super);
    function Dropdown(props) {
        var _this = this;
        props.options.forEach(function (option) {
            if (!option.itemType) {
                option.itemType = Dropdown_Props_1.DropdownMenuItemType.Normal;
            }
        });
        _this = _super.call(this, props) || this;
        _this._warnDeprecations({
            'isDisabled': 'disabled'
        });
        _this._warnMutuallyExclusive({
            'defaultSelectedKey': 'selectedKey'
        });
        _this._id = props.id || Utilities_1.getId('Dropdown');
        var selectedKey = props.defaultSelectedKey !== undefined ? props.defaultSelectedKey : props.selectedKey;
        _this.state = {
            isOpen: false,
            selectedIndex: _this._getSelectedIndex(props.options, selectedKey)
        };
        return _this;
    }
    Dropdown.prototype.componentWillReceiveProps = function (newProps) {
        // In controlled component usage where selectedKey is provided, update the selectedIndex
        // state if the key or options change.
        if (newProps.selectedKey !== undefined &&
            (newProps.selectedKey !== this.props.selectedKey || newProps.options !== this.props.options)) {
            this.setState({
                selectedIndex: this._getSelectedIndex(newProps.options, newProps.selectedKey)
            });
        }
    };
    Dropdown.prototype.componentDidUpdate = function (prevProps, prevState) {
        if (prevState.isOpen === true && this.state.isOpen === false) {
            this._dropDown.focus();
        }
    };
    // Primary Render
    Dropdown.prototype.render = function () {
        var id = this._id;
        var _a = this.props, className = _a.className, label = _a.label, options = _a.options, disabled = _a.disabled, isDisabled = _a.isDisabled, ariaLabel = _a.ariaLabel, required = _a.required, errorMessage = _a.errorMessage, _b = _a.onRenderTitle, onRenderTitle = _b === void 0 ? this._onRenderTitle : _b, _c = _a.onRenderContainer, onRenderContainer = _c === void 0 ? this._onRenderContainer : _c, _d = _a.onRenderPlaceHolder, onRenderPlaceHolder = _d === void 0 ? this._onRenderPlaceHolder : _d;
        var _e = this.state, isOpen = _e.isOpen, selectedIndex = _e.selectedIndex;
        var selectedOption = options[selectedIndex];
        // Remove this deprecation workaround at 1.0.0
        if (isDisabled !== undefined) {
            disabled = isDisabled;
        }
        return (React.createElement("div", { ref: 'root', className: Utilities_1.css('ms-Dropdown-container') },
            label && (React.createElement(Label_1.Label, { className: Utilities_1.css('ms-Dropdown-label'), id: id + '-label', ref: this._resolveRef('_dropdownLabel'), required: required }, label)),
            React.createElement("div", { "data-is-focusable": !disabled, ref: this._resolveRef('_dropDown'), id: id, className: Utilities_1.css('ms-Dropdown', styles.root, className, (_f = {
                        'is-open': isOpen
                    },
                    _f['is-disabled ' + styles.rootIsDisabled] = disabled,
                    _f['is-required '] = required,
                    _f)), tabIndex: disabled ? -1 : 0, onKeyDown: this._onDropdownKeyDown, onKeyUp: this._onDropdownKeyUp, onClick: this._onDropdownClick, "aria-expanded": isOpen ? 'true' : 'false', role: 'combobox', "aria-live": disabled || isOpen ? 'off' : 'assertive', "aria-label": ariaLabel || label, "aria-describedby": id + '-option', "aria-activedescendant": isOpen && selectedIndex >= 0 ? (this._id + '-list' + selectedIndex) : null, "aria-disabled": disabled, "aria-owns": isOpen ? id + '-list' : null },
                React.createElement("span", { id: id + '-option', className: Utilities_1.css('ms-Dropdown-title', styles.title, !selectedOption && styles.titleIsPlaceHolder, (errorMessage && errorMessage.length > 0 ? styles.titleIsError : null)), key: selectedIndex, "aria-atomic": true, role: 'textbox', "aria-readonly": 'true' }, // If option is selected render title, otherwise render the placeholder text
                selectedOption ? (onRenderTitle(selectedOption, this._onRenderTitle)) :
                    onRenderPlaceHolder(this.props, this._onRenderPlaceHolder)),
                React.createElement(Icon_1.Icon, { className: Utilities_1.css('ms-Dropdown-caretDown', styles.caretDown), iconName: 'ChevronDown' })),
            isOpen && (onRenderContainer(this.props, this._onRenderContainer)),
            errorMessage &&
                React.createElement("div", { className: Utilities_1.css(styles.errorMessage) }, errorMessage)));
        var _f;
    };
    Dropdown.prototype.focus = function () {
        if (this._dropDown && this._dropDown.tabIndex !== -1) {
            this._dropDown.focus();
        }
    };
    Dropdown.prototype.setSelectedIndex = function (index) {
        var _a = this.props, onChanged = _a.onChanged, options = _a.options, selectedKey = _a.selectedKey;
        var selectedIndex = this.state.selectedIndex;
        index = Math.max(0, Math.min(options.length - 1, index));
        if (index !== selectedIndex) {
            if (selectedKey === undefined) {
                // Set the selected option if this is an uncontrolled component
                this.setState({
                    selectedIndex: index
                });
            }
            if (onChanged) {
                onChanged(options[index], index);
            }
        }
    };
    /**
     * Finds the next valid Dropdown option and sets the selected index to it.
     * @param stepValue Value of how many items the function should traverse.  Should be -1 or 1.
     * @param index Index of where the search should start
     * @param selectedIndex The selectedIndex Dropdown's state
     * @returns The next valid dropdown option's index
     */
    Dropdown.prototype._moveIndex = function (stepValue, index, selectedIndex) {
        var options = this.props.options;
        // Return selectedIndex if nothing has changed or options is empty
        if (selectedIndex === index || options.length === 0) {
            return selectedIndex;
        }
        // Set starting index to 0 if index is < 0
        if (index < 0) {
            index = 0;
        }
        // Set starting index to last option index if greater than options.length
        if (index >= options.length) {
            index = options.length - 1;
        }
        var stepCounter = 0;
        // If current index is a header or divider, increment by step
        while (options[index].itemType !== Dropdown_Props_1.DropdownMenuItemType.Normal) {
            // If stepCounter exceeds length of options, then return selectedIndex (-1)
            if (stepCounter >= options.length) {
                return selectedIndex;
            }
            // If index + stepValue is out of bounds, reverse step direction
            if (index + stepValue < 0 || index + stepValue >= options.length) {
                stepValue *= -1;
            }
            index = index + stepValue;
            stepCounter++;
        }
        this.setSelectedIndex(index);
        return index;
    };
    // Render text in dropdown input
    Dropdown.prototype._onRenderTitle = function (item) {
        return React.createElement("span", null, item.text);
    };
    // Render placeHolder text in dropdown input
    Dropdown.prototype._onRenderPlaceHolder = function (props) {
        if (!props.placeHolder) {
            return null;
        }
        return React.createElement("span", null, props.placeHolder);
    };
    // Render Callout or Panel container and pass in list
    Dropdown.prototype._onRenderContainer = function (props) {
        var _a = this.props, _b = _a.onRenderList, onRenderList = _b === void 0 ? this._onRenderList : _b, responsiveMode = _a.responsiveMode, calloutProps = _a.calloutProps;
        var isSmall = responsiveMode <= withResponsiveMode_1.ResponsiveMode.medium;
        return (isSmall ?
            React.createElement(Panel_1.Panel, { className: Utilities_1.css('ms-Dropdown-panel', styles.panel), isOpen: true, isLightDismiss: true, onDismissed: this._onDismiss, hasCloseButton: false }, onRenderList(props, this._onRenderList))
            :
                React.createElement(Callout_1.Callout, tslib_1.__assign({ isBeakVisible: false, gapSpace: 0, doNotLayer: false, directionalHint: DirectionalHint_1.DirectionalHint.bottomLeftEdge }, calloutProps, { className: Utilities_1.css('ms-Dropdown-callout', styles.callout, calloutProps ? calloutProps.className : undefined), targetElement: this._dropDown, onDismiss: this._onDismiss, onPositioned: this._onPositioned }),
                    React.createElement("div", { style: { width: this._dropDown.clientWidth - 2 } }, onRenderList(props, this._onRenderList))));
    };
    // Render List of items
    Dropdown.prototype._onRenderList = function (props) {
        var _this = this;
        var _a = this.props.onRenderItem, onRenderItem = _a === void 0 ? this._onRenderItem : _a;
        var id = this._id;
        var selectedIndex = this.state.selectedIndex;
        return (React.createElement(FocusZone_1.FocusZone, { ref: this._resolveRef('_focusZone'), direction: FocusZone_1.FocusZoneDirection.vertical, defaultActiveElement: '#' + id + '-list' + selectedIndex, id: id + '-list', className: Utilities_1.css('ms-Dropdown-items', styles.items), "aria-labelledby": id + '-label', onKeyDown: this._onZoneKeyDown, role: 'listbox' }, this.props.options.map(function (item, index) { return onRenderItem(tslib_1.__assign({}, item, { index: index }), _this._onRenderItem); })));
    };
    // Render items
    Dropdown.prototype._onRenderItem = function (item) {
        switch (item.itemType) {
            case SelectableOption_Props_1.SelectableOptionMenuItemType.Divider:
                return this._renderSeparator(item);
            case SelectableOption_Props_1.SelectableOptionMenuItemType.Header:
                return this._renderHeader(item);
            default:
                return this._renderOption(item);
        }
    };
    // Render separator
    Dropdown.prototype._renderSeparator = function (item) {
        var index = item.index, key = item.key;
        if (index > 0) {
            return React.createElement("div", { role: 'separator', key: key, className: Utilities_1.css('ms-Dropdown-divider', styles.divider) });
        }
        return null;
    };
    Dropdown.prototype._renderHeader = function (item) {
        var _a = this.props.onRenderOption, onRenderOption = _a === void 0 ? this._onRenderOption : _a;
        var key = item.key;
        return (React.createElement("div", { key: key, className: Utilities_1.css('ms-Dropdown-header', styles.header) }, onRenderOption(item, this._onRenderOption)));
    };
    // Render menu item
    Dropdown.prototype._renderOption = function (item) {
        var _this = this;
        var _a = this.props.onRenderOption, onRenderOption = _a === void 0 ? this._onRenderOption : _a;
        var id = this._id;
        return (React.createElement(Button_1.CommandButton, { id: id + '-list' + item.index, ref: Dropdown_1.Option + item.index, key: item.key, "data-index": item.index, "data-is-focusable": true, className: Utilities_1.css('ms-Dropdown-item', styles.item, (_b = {},
                _b['is-selected ' + styles.itemIsSelected] = this.state.selectedIndex === item.index,
                _b['is-disabled ' + styles.itemIsDisabled] = this.props.disabled === true,
                _b)), onClick: function () { return _this._onItemClick(item.index); }, role: 'option', "aria-selected": this.state.selectedIndex === item.index ? 'true' : 'false', ariaLabel: item.ariaLabel || item.text, title: item.text },
            " ",
            onRenderOption(item, this._onRenderOption)));
        var _b;
    };
    // Render content of item (i.e. text/icon inside of button)
    Dropdown.prototype._onRenderOption = function (item) {
        return React.createElement("span", { className: Utilities_1.css('ms-Dropdown-optionText', styles.optionText) }, item.text);
    };
    Dropdown.prototype._onPositioned = function () {
        this._focusZone.focus();
    };
    Dropdown.prototype._onItemClick = function (index) {
        this.setSelectedIndex(index);
        this.setState({
            isOpen: false
        });
    };
    Dropdown.prototype._onDismiss = function () {
        this.setState({ isOpen: false });
        this._dropDown.focus();
    };
    Dropdown.prototype._getSelectedIndex = function (options, selectedKey) {
        return Utilities_1.findIndex(options, (function (option) { return (option.isSelected || option.selected || (selectedKey != null) && option.key === selectedKey); }));
    };
    Dropdown.prototype._onDropdownKeyDown = function (ev) {
        var newIndex;
        var selectedIndex = this.state.selectedIndex;
        switch (ev.which) {
            case Utilities_1.KeyCodes.enter:
                this.setState({
                    isOpen: !this.state.isOpen
                });
                break;
            case Utilities_1.KeyCodes.escape:
                if (!this.state.isOpen) {
                    return;
                }
                this.setState({
                    isOpen: false
                });
                break;
            case Utilities_1.KeyCodes.up:
                newIndex = this._moveIndex(-1, selectedIndex - 1, selectedIndex);
                break;
            case Utilities_1.KeyCodes.down:
                if (ev.altKey || ev.metaKey) {
                    this.setState({ isOpen: true });
                }
                else {
                    newIndex = this._moveIndex(1, selectedIndex + 1, selectedIndex);
                }
                break;
            case Utilities_1.KeyCodes.home:
                newIndex = this._moveIndex(1, 0, selectedIndex);
                break;
            case Utilities_1.KeyCodes.end:
                newIndex = this._moveIndex(-1, this.props.options.length - 1, selectedIndex);
                break;
            case Utilities_1.KeyCodes.space:
                // event handled in _onDropdownKeyUp
                break;
            default:
                return;
        }
        if (newIndex !== selectedIndex) {
            ev.stopPropagation();
            ev.preventDefault();
        }
    };
    Dropdown.prototype._onDropdownKeyUp = function (ev) {
        switch (ev.which) {
            case Utilities_1.KeyCodes.space:
                this.setState({
                    isOpen: !this.state.isOpen
                });
                break;
            default:
                return;
        }
        ev.stopPropagation();
        ev.preventDefault();
    };
    Dropdown.prototype._onZoneKeyDown = function (ev) {
        switch (ev.which) {
            case Utilities_1.KeyCodes.up:
                if (ev.altKey || ev.metaKey) {
                    this.setState({ isOpen: false });
                    break;
                }
                return;
            case Utilities_1.KeyCodes.escape:
                this.setState({ isOpen: false });
                break;
            case Utilities_1.KeyCodes.tab:
                this.setState({ isOpen: false });
                return;
            default:
                return;
        }
        ev.stopPropagation();
        ev.preventDefault();
    };
    Dropdown.prototype._onDropdownClick = function () {
        var _a = this.props, disabled = _a.disabled, isDisabled = _a.isDisabled;
        var isOpen = this.state.isOpen;
        // Remove this deprecation workaround at 1.0.0
        if (isDisabled !== undefined) {
            disabled = isDisabled;
        }
        if (!disabled) {
            this.setState({
                isOpen: !isOpen
            });
        }
    };
    return Dropdown;
}(Utilities_1.BaseComponent));
Dropdown.defaultProps = {
    options: []
};
Dropdown.Option = 'option';
tslib_1.__decorate([
    Utilities_1.autobind
], Dropdown.prototype, "_onRenderTitle", null);
tslib_1.__decorate([
    Utilities_1.autobind
], Dropdown.prototype, "_onRenderPlaceHolder", null);
tslib_1.__decorate([
    Utilities_1.autobind
], Dropdown.prototype, "_onRenderContainer", null);
tslib_1.__decorate([
    Utilities_1.autobind
], Dropdown.prototype, "_onRenderList", null);
tslib_1.__decorate([
    Utilities_1.autobind
], Dropdown.prototype, "_onRenderItem", null);
tslib_1.__decorate([
    Utilities_1.autobind
], Dropdown.prototype, "_renderOption", null);
tslib_1.__decorate([
    Utilities_1.autobind
], Dropdown.prototype, "_onRenderOption", null);
tslib_1.__decorate([
    Utilities_1.autobind
], Dropdown.prototype, "_onPositioned", null);
tslib_1.__decorate([
    Utilities_1.autobind
], Dropdown.prototype, "_onDismiss", null);
tslib_1.__decorate([
    Utilities_1.autobind
], Dropdown.prototype, "_onDropdownKeyDown", null);
tslib_1.__decorate([
    Utilities_1.autobind
], Dropdown.prototype, "_onDropdownKeyUp", null);
tslib_1.__decorate([
    Utilities_1.autobind
], Dropdown.prototype, "_onZoneKeyDown", null);
tslib_1.__decorate([
    Utilities_1.autobind
], Dropdown.prototype, "_onDropdownClick", null);
Dropdown = Dropdown_1 = tslib_1.__decorate([
    withResponsiveMode_1.withResponsiveMode
], Dropdown);
exports.Dropdown = Dropdown;
var Dropdown_1;
//# sourceMappingURL=Dropdown.js.map