UNPKG

office-ui-fabric-react

Version:

Reusable React components for building experiences for Office 365.

793 lines • 40.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var tslib_1 = require("tslib"); var React = require("react"); var Utilities_1 = require("../../Utilities"); var Callout_1 = require("../../Callout"); var Checkbox_1 = require("../../Checkbox"); var Button_1 = require("../../Button"); var DirectionalHint_1 = require("../../common/DirectionalHint"); var Dropdown_types_1 = require("./Dropdown.types"); var DropdownSizePosCache_1 = require("./utilities/DropdownSizePosCache"); var FocusZone_1 = require("../../FocusZone"); var Icon_1 = require("../../Icon"); var Label_1 = require("../../Label"); var KeytipData_1 = require("../../KeytipData"); var Panel_1 = require("../../Panel"); var withResponsiveMode_1 = require("../../utilities/decorators/withResponsiveMode"); var index_1 = require("../../utilities/selectableOption/index"); var getClassNames = Utilities_1.classNamesFunction(); var DropdownBase = /** @class */ (function (_super) { tslib_1.__extends(DropdownBase, _super); function DropdownBase(props) { var _this = _super.call(this, props) || this; _this._host = React.createRef(); _this._focusZone = React.createRef(); _this._dropDown = React.createRef(); _this._scrollIdleDelay = 250 /* ms */; _this._sizePosCache = new DropdownSizePosCache_1.DropdownSizePosCache(); _this._requestAnimationFrame = Utilities_1.safeRequestAnimationFrame(_this); _this._onChange = function (event, options, index, checked, multiSelect) { var _a = _this.props, onChange = _a.onChange, onChanged = _a.onChanged; if (onChange) { // for single-select, option passed in will always be selected. // for multi-select, flip the checked value var changedOpt = multiSelect ? tslib_1.__assign({}, options[index], { selected: !checked }) : options[index]; onChange(tslib_1.__assign({}, event, { target: _this._dropDown.current }), changedOpt, index); } if (onChanged) { // for single-select, option passed in will always be selected. // for multi-select, flip the checked value var changedOpt = multiSelect ? tslib_1.__assign({}, options[index], { selected: !checked }) : options[index]; onChanged(changedOpt, index); } }; /** Render text in dropdown input */ _this._onRenderTitle = function (items) { var _a = _this.props.multiSelectDelimiter, multiSelectDelimiter = _a === void 0 ? ', ' : _a; var displayTxt = items.map(function (i) { return i.text; }).join(multiSelectDelimiter); return React.createElement("span", null, displayTxt); }; /** Render placeholder text in dropdown input */ _this._onRenderPlaceholder = function (props) { if (!_this._placeholder) { return null; } return React.createElement("span", null, _this._placeholder); }; /** Render Callout or Panel container and pass in list */ _this._onRenderContainer = function (props) { var calloutProps = props.calloutProps, panelProps = props.panelProps; var _a = _this.props, responsiveMode = _a.responsiveMode, dropdownWidth = _a.dropdownWidth; var isSmall = responsiveMode <= withResponsiveMode_1.ResponsiveMode.medium; var panelStyles = _this._classNames.subComponentStyles ? _this._classNames.subComponentStyles.panel : undefined; return isSmall ? (React.createElement(Panel_1.Panel, tslib_1.__assign({ isOpen: true, isLightDismiss: true, onDismiss: _this._onDismiss, hasCloseButton: false, styles: panelStyles }, panelProps), _this._renderFocusableList(props))) : (React.createElement(Callout_1.Callout, tslib_1.__assign({ isBeakVisible: false, gapSpace: 0, doNotLayer: false, directionalHintFixed: false, directionalHint: DirectionalHint_1.DirectionalHint.bottomLeftEdge }, calloutProps, { className: _this._classNames.callout, target: _this._dropDown.current, onDismiss: _this._onDismiss, onScroll: _this._onScroll, onPositioned: _this._onPositioned, calloutWidth: dropdownWidth || (_this._dropDown.current ? _this._dropDown.current.clientWidth : 0) }), _this._renderFocusableList(props))); }; /** Render Caret Down Icon */ _this._onRenderCaretDown = function (props) { return React.createElement(Icon_1.Icon, { className: _this._classNames.caretDown, iconName: "ChevronDown", "aria-hidden": true }); }; /** Render List of items */ _this._onRenderList = function (props) { var _a = props.onRenderItem, onRenderItem = _a === void 0 ? _this._onRenderItem : _a; return React.createElement(React.Fragment, null, props.options.map(function (item, index) { return onRenderItem(tslib_1.__assign({}, item, { index: index }), _this._onRenderItem); })); }; _this._onRenderItem = function (item) { switch (item.itemType) { case index_1.SelectableOptionMenuItemType.Divider: return _this._renderSeparator(item); case index_1.SelectableOptionMenuItemType.Header: return _this._renderHeader(item); default: return _this._renderOption(item); } }; _this._renderOption = function (item) { var _a = _this.props.onRenderOption, onRenderOption = _a === void 0 ? _this._onRenderOption : _a; var _b = _this.state.selectedIndices, selectedIndices = _b === void 0 ? [] : _b; var id = _this._id; var isItemSelected = item.index !== undefined && selectedIndices ? selectedIndices.indexOf(item.index) > -1 : false; // select the right className based on the combination of selected/disabled var itemClassName = item.hidden // predicate: item hidden ? _this._classNames.dropdownItemHidden : isItemSelected && item.disabled === true // predicate: both selected and disabled ? _this._classNames.dropdownItemSelectedAndDisabled : isItemSelected // predicate: selected only ? _this._classNames.dropdownItemSelected : item.disabled === true // predicate: disabled only ? _this._classNames.dropdownItemDisabled : _this._classNames.dropdownItem; var _c = item.title, title = _c === void 0 ? item.text : _c; return !_this.props.multiSelect ? (React.createElement(Button_1.CommandButton, { id: id + '-list' + item.index, key: item.key, "data-index": item.index, "data-is-focusable": !item.disabled, disabled: item.disabled, className: itemClassName, onClick: _this._onItemClick(item), onMouseEnter: _this._onItemMouseEnter.bind(_this, item), onMouseLeave: _this._onMouseItemLeave.bind(_this, item), onMouseMove: _this._onItemMouseMove.bind(_this, item), role: "option", "aria-selected": isItemSelected ? 'true' : 'false', ariaLabel: item.ariaLabel, title: title }, onRenderOption(item, _this._onRenderOption))) : (React.createElement(Checkbox_1.Checkbox, { id: id + '-list' + item.index, key: item.key, "data-index": item.index, "data-is-focusable": !item.disabled, disabled: item.disabled, onChange: _this._onItemClick(item), inputProps: { onMouseEnter: _this._onItemMouseEnter.bind(_this, item), onMouseLeave: _this._onMouseItemLeave.bind(_this, item), onMouseMove: _this._onItemMouseMove.bind(_this, item) }, label: item.text, title: item.title ? item.title : item.text, onRenderLabel: _this._onRenderItemLabel.bind(_this, item), className: itemClassName, role: "option", "aria-selected": isItemSelected ? 'true' : 'false', checked: isItemSelected })); }; /** Render content of item (i.e. text/icon inside of button) */ _this._onRenderOption = function (item) { return React.createElement("span", { className: _this._classNames.dropdownOptionText }, item.text); }; /** Render custom label for drop down item */ _this._onRenderItemLabel = function (item) { var _a = _this.props.onRenderOption, onRenderOption = _a === void 0 ? _this._onRenderOption : _a; return onRenderOption(item, _this._onRenderOption); }; _this._onPositioned = function (positions) { if (_this._focusZone.current) { // Focusing an element can trigger a reflow. Making this wait until there is an animation // frame can improve perf significantly. _this._requestAnimationFrame(function () { var selectedIndices = _this.state.selectedIndices; if (_this._focusZone.current) { if (selectedIndices && selectedIndices[0] && !_this.props.options[selectedIndices[0]].disabled) { var element = Utilities_1.getDocument().querySelector("#" + _this._id + "-list" + selectedIndices[0]); _this._focusZone.current.focusElement(element); } else { _this._focusZone.current.focus(); } } }); } if (!_this.state.calloutRenderEdge || _this.state.calloutRenderEdge !== positions.targetEdge) { _this.setState({ calloutRenderEdge: positions.targetEdge }); } }; _this._onItemClick = function (item) { return function (event) { if (!item.disabled) { _this.setSelectedIndex(event, item.index); if (!_this.props.multiSelect) { // only close the callout when it's in single-select mode _this.setState({ isOpen: false }); } } }; }; /** * Scroll handler for the callout to make sure the mouse events * for updating focus are not interacting during scroll */ _this._onScroll = function () { if (!_this._isScrollIdle && _this._scrollIdleTimeoutId !== undefined) { clearTimeout(_this._scrollIdleTimeoutId); _this._scrollIdleTimeoutId = undefined; } else { _this._isScrollIdle = false; } _this._scrollIdleTimeoutId = setTimeout(function () { _this._isScrollIdle = true; }, _this._scrollIdleDelay); }; _this._onMouseItemLeave = function (item, ev) { if (_this._shouldIgnoreMouseEvent()) { return; } /** * IE11 focus() method forces parents to scroll to top of element. * Edge and IE expose a setActive() function for focusable divs that * sets the page focus but does not scroll the parent element. */ if (_this._host.current) { if (_this._host.current.setActive) { try { _this._host.current.setActive(); } catch (e) { /* no-op */ } } else { _this._host.current.focus(); } } }; _this._onDismiss = function () { _this.setState({ isOpen: false }); if (_this._dropDown.current) { _this._dropDown.current.focus(); } }; _this._onDropdownBlur = function (ev) { // If Dropdown disabled do not proceed with this logic. var disabled = _this._isDisabled(); if (disabled) { return; } // hasFocus tracks whether the root element has focus so always update the state. _this.setState({ hasFocus: false }); if (_this.state.isOpen) { // Do not onBlur when the callout is opened return; } if (_this.props.onBlur) { _this.props.onBlur(ev); } }; _this._onDropdownKeyDown = function (ev) { // If Dropdown disabled do not process any keyboard events. var disabled = _this._isDisabled(); if (disabled) { return; } // Take note if we are processing an alt (option) or meta (command) keydown. // See comment in _shouldHandleKeyUp for reasoning. _this._lastKeyDownWasAltOrMeta = _this._isAltOrMeta(ev); if (_this.props.onKeyDown) { _this.props.onKeyDown(ev); if (ev.defaultPrevented) { return; } } var newIndex; var selectedIndex = _this.state.selectedIndices.length ? _this.state.selectedIndices[0] : -1; var containsExpandCollapseModifier = ev.altKey || ev.metaKey; var isOpen = _this.state.isOpen; switch (ev.which) { case Utilities_1.KeyCodes.enter: _this.setState({ isOpen: !isOpen }); break; case Utilities_1.KeyCodes.escape: if (!isOpen) { return; } _this.setState({ isOpen: false }); break; case Utilities_1.KeyCodes.up: if (containsExpandCollapseModifier) { if (isOpen) { _this.setState({ isOpen: false }); break; } return; } if (_this.props.multiSelect) { _this.setState({ isOpen: true }); } else if (!_this._isDisabled()) { newIndex = _this._moveIndex(ev, -1, selectedIndex - 1, selectedIndex); } break; case Utilities_1.KeyCodes.down: if (containsExpandCollapseModifier) { ev.stopPropagation(); ev.preventDefault(); } if ((containsExpandCollapseModifier && !isOpen) || _this.props.multiSelect) { _this.setState({ isOpen: true }); } else if (!_this._isDisabled()) { newIndex = _this._moveIndex(ev, 1, selectedIndex + 1, selectedIndex); } break; case Utilities_1.KeyCodes.home: if (!_this.props.multiSelect) { newIndex = _this._moveIndex(ev, 1, 0, selectedIndex); } break; case Utilities_1.KeyCodes.end: if (!_this.props.multiSelect) { newIndex = _this._moveIndex(ev, -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(); } }; _this._onDropdownKeyUp = function (ev) { // If Dropdown disabled do not process any keyboard events. var disabled = _this._isDisabled(); if (disabled) { return; } var shouldHandleKey = _this._shouldHandleKeyUp(ev); var isOpen = _this.state.isOpen; if (_this.props.onKeyUp) { _this.props.onKeyUp(ev); if (ev.preventDefault) { return; } } switch (ev.which) { case Utilities_1.KeyCodes.space: _this.setState({ isOpen: !isOpen }); break; default: if (shouldHandleKey && isOpen) { _this.setState({ isOpen: false }); } return; } ev.stopPropagation(); ev.preventDefault(); }; _this._onZoneKeyDown = function (ev) { var elementToFocus; // Take note if we are processing an alt (option) or meta (command) keydown. // See comment in _shouldHandleKeyUp for reasoning. _this._lastKeyDownWasAltOrMeta = _this._isAltOrMeta(ev); var containsExpandCollapseModifier = ev.altKey || ev.metaKey; switch (ev.which) { case Utilities_1.KeyCodes.up: if (containsExpandCollapseModifier) { _this.setState({ isOpen: false }); } else { if (_this._host.current) { elementToFocus = Utilities_1.getLastFocusable(_this._host.current, _this._host.current.lastChild, true); } } 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 Utilities_1.KeyCodes.home: case Utilities_1.KeyCodes.end: case Utilities_1.KeyCodes.pageUp: case Utilities_1.KeyCodes.pageDown: break; case Utilities_1.KeyCodes.down: if (!containsExpandCollapseModifier && _this._host.current) { elementToFocus = Utilities_1.getFirstFocusable(_this._host.current, _this._host.current.firstChild, true); } break; case Utilities_1.KeyCodes.escape: _this.setState({ isOpen: false }); break; case Utilities_1.KeyCodes.tab: _this.setState({ isOpen: false }); return; default: return; } if (elementToFocus) { elementToFocus.focus(); } ev.stopPropagation(); ev.preventDefault(); }; _this._onZoneKeyUp = function (ev) { var shouldHandleKey = _this._shouldHandleKeyUp(ev); if (shouldHandleKey && _this.state.isOpen) { _this.setState({ isOpen: false }); ev.preventDefault(); } }; _this._onDropdownClick = function (ev) { if (_this.props.onClick) { _this.props.onClick(ev); if (ev.preventDefault) { return; } } var isOpen = _this.state.isOpen; var disabled = _this._isDisabled(); if (!disabled) { _this.setState({ isOpen: !isOpen }); } }; _this._onFocus = function (ev) { var _a = _this.state, isOpen = _a.isOpen, selectedIndices = _a.selectedIndices, hasFocus = _a.hasFocus; var _b = _this.props, multiSelect = _b.multiSelect, openOnKeyboardFocus = _b.openOnKeyboardFocus; var disabled = _this._isDisabled(); if (!disabled) { if (!isOpen && selectedIndices.length === 0 && !multiSelect) { // Per aria _this._moveIndex(ev, 1, 0, -1); } if (_this.props.onFocus) { _this.props.onFocus(ev); } var state = { hasFocus: true }; if (openOnKeyboardFocus && !hasFocus) { state.isOpen = true; } _this.setState(state); } }; /** * Because the isDisabled prop is deprecated, we have had to repeat this logic all over the place. * This helper method avoids all the repetition. */ _this._isDisabled = function () { var disabled = _this.props.disabled; var isDisabled = _this.props.isDisabled; // Remove this deprecation workaround at 1.0.0 if (isDisabled !== undefined) { disabled = isDisabled; } return disabled; }; _this._onRenderLabel = function (props) { var id = _this._id; var label = props.label, required = props.required, disabled = props.disabled; var labelStyles = _this._classNames.subComponentStyles ? _this._classNames.subComponentStyles.label : undefined; return label ? (React.createElement(Label_1.Label, { className: _this._classNames.label, id: id + '-label', htmlFor: id, required: required, styles: labelStyles, disabled: disabled }, label)) : null; }; Utilities_1.initializeComponentRef(_this); if (process.env.NODE_ENV !== 'production') { Utilities_1.warnDeprecations('Dropdown', props, { isDisabled: 'disabled', onChanged: 'onChange', placeHolder: 'placeholder', onRenderPlaceHolder: 'onRenderPlaceholder' }); Utilities_1.warnMutuallyExclusive('Dropdown', props, { defaultSelectedKey: 'selectedKey', defaultSelectedKeys: 'selectedKeys', selectedKeys: 'selectedKey', multiSelect: 'defaultSelectedKey', selectedKey: 'multiSelect' }); } _this._id = props.id || Utilities_1.getId('Dropdown'); _this._isScrollIdle = true; var selectedIndices; if (_this.props.multiSelect) { var selectedKeys = props.defaultSelectedKeys !== undefined ? props.defaultSelectedKeys : props.selectedKeys; selectedIndices = _this._getSelectedIndexes(props.options, selectedKeys); } else { var selectedKey = props.defaultSelectedKey !== undefined ? props.defaultSelectedKey : props.selectedKey; selectedIndices = _this._getSelectedIndexes(props.options, selectedKey); _this._sizePosCache.updateOptions(props.options); } _this.state = { isOpen: false, selectedIndices: selectedIndices, hasFocus: false, calloutRenderEdge: undefined }; return _this; } Object.defineProperty(DropdownBase.prototype, "selectedOptions", { /** * All selected options */ get: function () { var options = this.props.options; var selectedIndices = this.state.selectedIndices; return index_1.getAllSelectedOptions(options, selectedIndices); }, enumerable: true, configurable: true }); DropdownBase.prototype.componentWillUnmount = function () { clearTimeout(this._scrollIdleTimeoutId); }; // tslint:disable-next-line function-name DropdownBase.prototype.UNSAFE_componentWillReceiveProps = function (newProps) { // In controlled component usage where selectedKey is provided, update the selectedIndex // state if the key or options change. var selectedKeyProp; // this does a shallow compare (assumes options are pure), for the purposes of determining whether // defaultSelectedKey/defaultSelectedKeys are respected. var didOptionsChange = newProps.options !== this.props.options; if (newProps.multiSelect) { if (didOptionsChange && newProps.defaultSelectedKeys !== undefined) { selectedKeyProp = 'defaultSelectedKeys'; } else { selectedKeyProp = 'selectedKeys'; } } else { if (didOptionsChange && newProps.defaultSelectedKey !== undefined) { selectedKeyProp = 'defaultSelectedKey'; } else { selectedKeyProp = 'selectedKey'; } } if (newProps[selectedKeyProp] !== undefined && (newProps[selectedKeyProp] !== this.props[selectedKeyProp] || didOptionsChange)) { this.setState({ selectedIndices: this._getSelectedIndexes(newProps.options, newProps[selectedKeyProp]) }); } if (newProps.options !== this.props.options && // preexisting code assumes purity of the options... !newProps.multiSelect // only relevant in single selection ) { this._sizePosCache.updateOptions(newProps.options); } }; DropdownBase.prototype.componentDidUpdate = function (prevProps, prevState) { if (prevState.isOpen === true && this.state.isOpen === false) { this._gotMouseMove = false; if (this._dropDown.current) { this._dropDown.current.focus(); } if (this.props.onDismiss) { this.props.onDismiss(); } } }; DropdownBase.prototype.render = function () { var _this = this; var id = this._id; var props = this.props; var className = props.className, label = props.label, options = props.options, ariaLabel = props.ariaLabel, required = props.required, errorMessage = props.errorMessage, multiSelect = props.multiSelect, keytipProps = props.keytipProps, propStyles = props.styles, theme = props.theme, panelProps = props.panelProps, calloutProps = props.calloutProps, _a = props.onRenderTitle, onRenderTitle = _a === void 0 ? this._onRenderTitle : _a, _b = props.onRenderContainer, onRenderContainer = _b === void 0 ? this._onRenderContainer : _b, _c = props.onRenderCaretDown, onRenderCaretDown = _c === void 0 ? this._onRenderCaretDown : _c, _d = props.onRenderLabel, onRenderLabel = _d === void 0 ? this._onRenderLabel : _d; var _e = this.state, isOpen = _e.isOpen, selectedIndices = _e.selectedIndices, hasFocus = _e.hasFocus, calloutRenderEdge = _e.calloutRenderEdge; var onRenderPlaceholder = props.onRenderPlaceholder || props.onRenderPlaceHolder || this._onRenderPlaceholder; var selectedOptions = index_1.getAllSelectedOptions(options, selectedIndices); var divProps = Utilities_1.getNativeProps(props, Utilities_1.divProperties); var disabled = this._isDisabled(); var optionId = id + '-option'; var ariaAttrs = multiSelect || disabled ? { role: undefined, ariaActiveDescendant: undefined, childRole: undefined, ariaSetSize: undefined, ariaPosInSet: undefined, ariaSelected: undefined } : // single select { role: 'listbox', ariaActiveDescendant: isOpen && selectedIndices.length === 1 && selectedIndices[0] >= 0 ? this._id + '-list' + selectedIndices[0] : optionId, childRole: 'option', ariaSetSize: this._sizePosCache.optionSetSize, ariaPosInSet: this._sizePosCache.positionInSet(selectedIndices[0]), ariaSelected: selectedIndices[0] === undefined ? undefined : true }; this._classNames = getClassNames(propStyles, { theme: theme, className: className, hasError: !!(errorMessage && errorMessage.length > 0), hasLabel: !!label, isOpen: isOpen, required: required, disabled: disabled, isRenderingPlaceholder: !selectedOptions.length, panelClassName: !!panelProps ? panelProps.className : undefined, calloutClassName: !!calloutProps ? calloutProps.className : undefined, calloutRenderEdge: calloutRenderEdge }); return (React.createElement("div", { className: this._classNames.root }, onRenderLabel(this.props, this._onRenderLabel), React.createElement(KeytipData_1.KeytipData, { keytipProps: keytipProps, disabled: disabled }, function (keytipAttributes) { return (React.createElement("div", tslib_1.__assign({}, keytipAttributes, { "data-is-focusable": !disabled, ref: _this._dropDown, id: id, tabIndex: disabled ? -1 : 0, "aria-expanded": isOpen ? 'true' : 'false', role: ariaAttrs.role, "aria-label": ariaLabel, "aria-labelledby": label && !ariaLabel ? id + '-label' : undefined, "aria-describedby": Utilities_1.mergeAriaAttributeValues(optionId, keytipAttributes['aria-describedby']), "aria-activedescendant": isOpen ? ariaAttrs.ariaActiveDescendant : undefined, "aria-required": required, "aria-disabled": disabled, "aria-owns": isOpen ? id + '-list' : undefined }, divProps, { className: _this._classNames.dropdown, onBlur: _this._onDropdownBlur, onKeyDown: _this._onDropdownKeyDown, onKeyUp: _this._onDropdownKeyUp, onClick: _this._onDropdownClick, onFocus: _this._onFocus }), React.createElement("span", { id: optionId, className: _this._classNames.title, "aria-atomic": true, role: ariaAttrs.childRole, "aria-live": !hasFocus || disabled || multiSelect || isOpen ? 'off' : 'assertive', "aria-label": selectedOptions.length ? selectedOptions[0].text : _this._placeholder, "aria-setsize": ariaAttrs.ariaSetSize, "aria-posinset": ariaAttrs.ariaPosInSet, "aria-selected": ariaAttrs.ariaSelected }, // If option is selected render title, otherwise render the placeholder text selectedOptions.length ? onRenderTitle(selectedOptions, _this._onRenderTitle) : onRenderPlaceholder(props, _this._onRenderPlaceholder)), React.createElement("span", { className: _this._classNames.caretDownWrapper }, onRenderCaretDown(props, _this._onRenderCaretDown)))); }), isOpen && onRenderContainer(props, this._onRenderContainer), errorMessage && errorMessage.length > 0 && React.createElement("div", { className: this._classNames.errorMessage }, errorMessage))); }; DropdownBase.prototype.focus = function (shouldOpenOnFocus) { if (this._dropDown.current) { this._dropDown.current.focus(); if (shouldOpenOnFocus) { this.setState({ isOpen: true }); } } }; DropdownBase.prototype.setSelectedIndex = function (event, index) { var _this = this; var _a = this.props, options = _a.options, selectedKey = _a.selectedKey, selectedKeys = _a.selectedKeys, multiSelect = _a.multiSelect, notifyOnReselect = _a.notifyOnReselect; var _b = this.state.selectedIndices, selectedIndices = _b === void 0 ? [] : _b; var checked = selectedIndices ? selectedIndices.indexOf(index) > -1 : false; var newIndexes = []; index = Math.max(0, Math.min(options.length - 1, index)); // If this is a controlled component then no state change should take place. if (selectedKey !== undefined || selectedKeys !== undefined) { this._onChange(event, options, index, checked, multiSelect); return; } if (!multiSelect && !notifyOnReselect && index === selectedIndices[0]) { return; } else if (multiSelect) { newIndexes = selectedIndices ? this._copyArray(selectedIndices) : []; 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); } } else { // Set the selected option if this is an uncontrolled component newIndexes = [index]; } event.persist(); // Call onChange after state is updated this.setState({ selectedIndices: newIndexes }, function () { _this._onChange(event, options, index, checked, multiSelect); }); }; Object.defineProperty(DropdownBase.prototype, "_placeholder", { /** Get either props.placeholder (new name) or props.placeHolder (old name) */ get: function () { return this.props.placeholder || this.props.placeHolder; }, enumerable: true, configurable: true }); DropdownBase.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 */ DropdownBase.prototype._moveIndex = function (event, 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, or disabled, increment by step while (options[index].itemType === Dropdown_types_1.DropdownMenuItemType.Header || options[index].itemType === Dropdown_types_1.DropdownMenuItemType.Divider || options[index].disabled) { // If stepCounter exceeds length of options, then return selectedIndex (-1) if (stepCounter >= options.length) { return selectedIndex; } // If index + stepValue is out of bounds, wrap around if (index + stepValue < 0) { index = options.length; } else if (index + stepValue >= options.length) { index = -1; } index = index + stepValue; stepCounter++; } this.setSelectedIndex(event, index); return index; }; /** Wrap item list in a FocusZone */ DropdownBase.prototype._renderFocusableList = function (props) { var _a = props.onRenderList, onRenderList = _a === void 0 ? this._onRenderList : _a, label = props.label, ariaLabel = props.ariaLabel; var id = this._id; return (React.createElement("div", { className: this._classNames.dropdownItemsWrapper, onKeyDown: this._onZoneKeyDown, onKeyUp: this._onZoneKeyUp, ref: this._host, tabIndex: 0 }, React.createElement(FocusZone_1.FocusZone, { ref: this._focusZone, direction: FocusZone_1.FocusZoneDirection.vertical, id: id + '-list', className: this._classNames.dropdownItems, "aria-label": ariaLabel, "aria-labelledby": label && !ariaLabel ? id + '-label' : undefined, role: "listbox" }, onRenderList(props, this._onRenderList)))); }; DropdownBase.prototype._renderSeparator = function (item) { var index = item.index, key = item.key; if (index > 0) { return React.createElement("div", { role: "separator", key: key, className: this._classNames.dropdownDivider }); } return null; }; DropdownBase.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: this._classNames.dropdownItemHeader }, onRenderOption(item, this._onRenderOption))); }; DropdownBase.prototype._onItemMouseEnter = function (item, ev) { if (this._shouldIgnoreMouseEvent()) { return; } var targetElement = ev.currentTarget; targetElement.focus(); }; DropdownBase.prototype._onItemMouseMove = function (item, ev) { var targetElement = ev.currentTarget; this._gotMouseMove = true; if (!this._isScrollIdle || document.activeElement === targetElement) { return; } targetElement.focus(); }; DropdownBase.prototype._shouldIgnoreMouseEvent = function () { return !this._isScrollIdle || !this._gotMouseMove; }; /** Get all selected indexes for multi-select mode */ DropdownBase.prototype._getSelectedIndexes = function (options, selectedKey) { if (selectedKey === undefined) { if (this.props.multiSelect) { return this._getAllSelectedIndices(options); } var selectedIndex = this._getSelectedIndex(options, null); return selectedIndex !== -1 ? [selectedIndex] : []; } else if (!Array.isArray(selectedKey)) { var selectedIndex = this._getSelectedIndex(options, selectedKey); return selectedIndex !== -1 ? [selectedIndex] : []; } var selectedIndices = []; for (var _i = 0, selectedKey_1 = selectedKey; _i < selectedKey_1.length; _i++) { var key = selectedKey_1[_i]; var selectedIndex = this._getSelectedIndex(options, key); selectedIndex !== -1 && selectedIndices.push(selectedIndex); } return selectedIndices; }; DropdownBase.prototype._getAllSelectedIndices = function (options) { return options.map(function (option, index) { return (option.selected ? index : -1); }).filter(function (index) { return index !== -1; }); }; DropdownBase.prototype._getSelectedIndex = function (options, selectedKey) { return Utilities_1.findIndex(options, function (option) { // tslint:disable-next-line:triple-equals if (selectedKey != null) { return option.key === selectedKey; } else { return !!option.isSelected || !!option.selected; } }); }; /** * Returns true if the key for the event is alt (Mac option) or meta (Mac command). */ DropdownBase.prototype._isAltOrMeta = function (ev) { return ev.which === Utilities_1.KeyCodes.alt || ev.key === 'Meta'; }; /** * 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. */ DropdownBase.prototype._shouldHandleKeyUp = function (ev) { var keyPressIsAltOrMetaAlone = this._lastKeyDownWasAltOrMeta && this._isAltOrMeta(ev); this._lastKeyDownWasAltOrMeta = false; return !!keyPressIsAltOrMetaAlone && !(Utilities_1.isMac() || Utilities_1.isIOS()); }; DropdownBase.defaultProps = { options: [] }; DropdownBase = tslib_1.__decorate([ withResponsiveMode_1.withResponsiveMode ], DropdownBase); return DropdownBase; }(React.Component)); exports.DropdownBase = DropdownBase; //# sourceMappingURL=Dropdown.base.js.map