office-ui-fabric-react
Version:
Reusable React components for building experiences for Office 365.
502 lines • 27.6 kB
JavaScript
define(["require", "exports", "tslib", "react", "./Dropdown.Props", "../../Checkbox", "../../common/DirectionalHint", "../../Callout", "../../Label", "../../Button", "../../Panel", "../../Icon", "../../FocusZone", "../../utilities/decorators/withResponsiveMode", "../../Utilities", "../../utilities/selectableOption/SelectableOption.Props", "./Dropdown.scss"], function (require, exports, tslib_1, React, Dropdown_Props_1, Checkbox_1, DirectionalHint_1, Callout_1, Label_1, Button_1, Panel_1, Icon_1, FocusZone_1, withResponsiveMode_1, Utilities_1, SelectableOption_Props_1, stylesImport) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var styles = stylesImport;
var Dropdown = (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',
'defaultSelectedKeys': 'selectedKeys',
'selectedKeys': 'selectedKey',
'multiSelect': 'defaultSelectedKey',
'selectedKey': 'multiSelect'
});
_this._id = props.id || Utilities_1.getId('Dropdown');
_this.state = {
isOpen: false
};
if (_this.props.multiSelect) {
var selectedKeys = props.defaultSelectedKeys !== undefined ? props.defaultSelectedKeys : props.selectedKeys;
_this.state = {
selectedIndexes: _this._getSelectedIndexes(props.options, selectedKeys)
};
}
else {
var selectedKey = props.defaultSelectedKey !== undefined ? props.defaultSelectedKey : props.selectedKey;
_this.state = {
selectedIndex: _this._getSelectedIndex(props.options, selectedKey)
};
}
return _this;
}
Dropdown_1 = Dropdown;
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)) {
if (this.props.multiSelect) {
this.setState({
selectedIndexes: this._getSelectedIndexes(newProps.options, newProps.selectedKeys)
});
}
else {
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, selectedIndexes = _e.selectedIndexes;
var selectedOption = this.props.multiSelect ? selectedIndexes && this._getAllSelectedOptions(options, selectedIndexes)
: options[selectedIndex];
var divProps = Utilities_1.getNativeProps(this.props, Utilities_1.divProperties);
// 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', htmlFor: id, ref: this._resolveRef('_dropdownLabel'), required: required }, label)),
React.createElement("div", tslib_1.__assign({ "data-is-focusable": !disabled, ref: this._resolveRef('_dropDown'), id: id, tabIndex: disabled ? -1 : 0, "aria-expanded": isOpen ? 'true' : 'false', role: 'menu', "aria-live": disabled || isOpen ? 'off' : 'assertive', "aria-label": ariaLabel, "aria-describedby": id + '-option', "aria-activedescendant": isOpen && selectedIndex >= 0 ? (this._id + '-list' + selectedIndex) : null, "aria-disabled": disabled, "aria-owns": isOpen ? id + '-list' : null }, divProps, { className: Utilities_1.css('ms-Dropdown', styles.root, className, isOpen && 'is-open', disabled && ('is-disabled ' + styles.rootIsDisabled), required && 'is-required'), onBlur: this._onDropdownBlur, onKeyDown: this._onDropdownKeyDown, onKeyUp: this._onDropdownKeyUp, onClick: this._onDropdownClick }),
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)));
};
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, selectedKeys = _a.selectedKeys, multiSelect = _a.multiSelect;
var _b = this.state, selectedIndex = _b.selectedIndex, selectedIndexes = _b.selectedIndexes;
var checked = selectedIndexes ? selectedIndexes.indexOf(index) > -1 : false;
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
if (multiSelect) {
var newIndexes = selectedIndexes ? this._copyArray(selectedIndexes) : [];
if (checked) {
var position = newIndexes.indexOf(index);
if (position > -1) {
// unchecked the current one
newIndexes.splice(position, 1);
}
}
else {
// add the new selected index into the existing one
newIndexes.push(index);
}
this.setState({
selectedIndexes: newIndexes
});
}
else {
this.setState({
selectedIndex: index
});
}
}
if (onChanged) {
// for single-select, option passed in will always be selected.
if (!multiSelect) {
options.map(function (option) { return option.selected === true ? option.selected = false : null; });
}
// for multi-select, flip the checked value
var changedOpt = options[index];
changedOpt.selected = multiSelect ? !checked : true;
onChanged(changedOpt, index);
}
}
};
Dropdown.prototype._copyArray = function (array) {
var newArray = [];
for (var _i = 0, array_1 = array; _i < array_1.length; _i++) {
var element = array_1[_i];
newArray.push(element);
}
return newArray;
};
/**
* 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.Header || options[index].itemType === Dropdown_Props_1.DropdownMenuItemType.Divider) {
// 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) {
var displayTxt = '';
var _a = this.props.multiSelectDelimiter, multiSelectDelimiter = _a === void 0 ? ', ' : _a;
if (this.props.multiSelect && Array.isArray(item)) {
displayTxt = item.map(function (i) { return i.text; }).join(multiSelectDelimiter);
}
else {
displayTxt = item.text;
}
return React.createElement("span", null, displayTxt);
};
// 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, dropdownWidth = _a.dropdownWidth;
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, directionalHintFixed: true, 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, calloutWidth: dropdownWidth || this._dropDown.clientWidth }), 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("div", { onKeyDown: this._onZoneKeyDown },
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', 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 selectedIndexes = this.state.selectedIndexes;
var id = this._id;
var isItemSelected;
if (this.props.multiSelect) {
isItemSelected = item.index !== undefined && selectedIndexes ? selectedIndexes.indexOf(item.index) > -1 : false;
}
return (!this.props.multiSelect ?
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))
:
React.createElement(Checkbox_1.Checkbox, { id: id + '-list' + item.index, ref: Dropdown_1.Option + item.index, key: item.key, "data-index": item.index, "data-is-focusable": true, onChange: function () { return _this._onItemClick(item.index); }, label: item.text, className: Utilities_1.css('ms-ColumnManagementPanel-checkbox', 'ms-Dropdown-item', styles.item, (_c = {},
_c['is-selected ' + styles.itemIsSelected] = isItemSelected,
_c['is-disabled ' + styles.itemIsDisabled] = isItemSelected,
_c)), role: 'option', "aria-selected": isItemSelected ? 'true' : 'false', checked: isItemSelected },
onRenderOption(item, this._onRenderOption),
" "));
var _b, _c;
};
// 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);
if (!this.props.multiSelect) {
// only close the callout when it's in single-select mode
this.setState({
isOpen: false
});
}
};
Dropdown.prototype._onDismiss = function () {
this.setState({ isOpen: false });
this._dropDown.focus();
};
// Get all selected indexes for multi-select mode
Dropdown.prototype._getSelectedIndexes = function (options, selectedKey) {
var selectedIndex = [];
if (!selectedKey) {
return selectedIndex;
}
for (var _i = 0, selectedKey_1 = selectedKey; _i < selectedKey_1.length; _i++) {
var key = selectedKey_1[_i];
selectedIndex.push(this._getSelectedIndex(options, key));
}
return selectedIndex;
};
// Get all selected options for multi-select mode
Dropdown.prototype._getAllSelectedOptions = function (options, selectedIndex) {
var selectedOptions = [];
for (var _i = 0, selectedIndex_1 = selectedIndex; _i < selectedIndex_1.length; _i++) {
var index = selectedIndex_1[_i];
selectedOptions.push(options[index]);
}
if (selectedOptions.length < 1) {
return undefined;
}
return selectedOptions;
};
Dropdown.prototype._getSelectedIndex = function (options, selectedKey) {
// tslint:disable-next-line:triple-equals
return Utilities_1.findIndex(options, (function (option) { return ((option.isSelected || option.selected || (selectedKey != null)) && option.key === selectedKey); }));
};
Dropdown.prototype._onDropdownBlur = function (ev) {
if (this.state.isOpen) {
// Do not onBlur when the callout is opened
return;
}
if (this.props.onBlur) {
this.props.onBlur(ev);
}
};
Dropdown.prototype._onDropdownKeyDown = function (ev) {
if (this.props.onKeyDown) {
this.props.onKeyDown(ev);
if (ev.preventDefault) {
return;
}
}
var newIndex;
var selectedIndex = this.props.multiSelect ? (this.state.selectedIndexes ? this.state.selectedIndexes[0] : -1) : this.state.selectedIndex;
switch (ev.which) {
case 13 /* enter */:
this.setState({
isOpen: !this.state.isOpen
});
break;
case 27 /* escape */:
if (!this.state.isOpen) {
return;
}
this.setState({
isOpen: false
});
break;
case 38 /* up */:
if (this.props.multiSelect) {
this.setState({ isOpen: true });
}
else {
newIndex = this._moveIndex(-1, selectedIndex - 1, selectedIndex);
}
break;
case 40 /* down */:
if (ev.altKey || ev.metaKey || this.props.multiSelect) {
this.setState({ isOpen: true });
}
else {
newIndex = this._moveIndex(1, selectedIndex + 1, selectedIndex);
}
break;
case 36 /* home */:
newIndex = this._moveIndex(1, 0, selectedIndex);
break;
case 35 /* end */:
newIndex = this._moveIndex(-1, this.props.options.length - 1, selectedIndex);
break;
case 32 /* space */:
// event handled in _onDropdownKeyUp
break;
default:
return;
}
if (newIndex !== selectedIndex) {
ev.stopPropagation();
ev.preventDefault();
}
};
Dropdown.prototype._onDropdownKeyUp = function (ev) {
if (this.props.onKeyUp) {
this.props.onKeyUp(ev);
if (ev.preventDefault) {
return;
}
}
switch (ev.which) {
case 32 /* space */:
this.setState({
isOpen: !this.state.isOpen
});
break;
default:
return;
}
ev.stopPropagation();
ev.preventDefault();
};
Dropdown.prototype._onZoneKeyDown = function (ev) {
switch (ev.which) {
case 38 /* up */:
if (ev.altKey || ev.metaKey) {
this.setState({ isOpen: false });
}
break;
// All directional keystrokes should be canceled when the zone is rendered.
// This avoids the body scroll from reacting and thus dismissing the dropdown.
case 36 /* home */:
case 35 /* end */:
case 33 /* pageUp */:
case 34 /* pageDown */:
case 40 /* down */:
break;
case 27 /* escape */:
this.setState({ isOpen: false });
break;
case 9 /* tab */:
this.setState({ isOpen: false });
return;
default:
return;
}
ev.stopPropagation();
ev.preventDefault();
};
Dropdown.prototype._onDropdownClick = function (ev) {
if (this.props.onClick) {
this.props.onClick(ev);
if (ev.preventDefault) {
return;
}
}
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
});
}
};
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, "_onDropdownBlur", 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);
return Dropdown;
var Dropdown_1;
}(Utilities_1.BaseComponent));
exports.Dropdown = Dropdown;
});
//# sourceMappingURL=Dropdown.js.map