@salesforce/design-system-react
Version:
Salesforce Lightning Design System for React
1,176 lines (1,028 loc) • 43.4 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
var _dialog = require('../utilities/dialog');
var _dialog2 = _interopRequireDefault(_dialog);
var _innerInput = require('../../components/forms/input/private/inner-input');
var _innerInput2 = _interopRequireDefault(_innerInput);
var _inputIcon = require('../icon/input-icon');
var _inputIcon2 = _interopRequireDefault(_inputIcon);
var _menu = require('./private/menu');
var _menu2 = _interopRequireDefault(_menu);
var _label = require('../forms/private/label');
var _label2 = _interopRequireDefault(_label);
var _selectedListbox = require('./private/selected-listbox');
var _selectedListbox2 = _interopRequireDefault(_selectedListbox);
var _lodash = require('lodash.assign');
var _lodash2 = _interopRequireDefault(_lodash);
var _lodash3 = require('lodash.find');
var _lodash4 = _interopRequireDefault(_lodash3);
var _lodash5 = require('lodash.reject');
var _lodash6 = _interopRequireDefault(_lodash5);
var _lodash7 = require('lodash.isequal');
var _lodash8 = _interopRequireDefault(_lodash7);
var _airbnbPropTypes = require('airbnb-prop-types');
var _lodash9 = require('lodash.isboolean');
var _lodash10 = _interopRequireDefault(_lodash9);
var _lodash11 = require('lodash.isfunction');
var _lodash12 = _interopRequireDefault(_lodash11);
var _classnames = require('classnames');
var _classnames2 = _interopRequireDefault(_classnames);
var _shortid = require('shortid');
var _shortid2 = _interopRequireDefault(_shortid);
var _keyCode = require('../../utilities/key-code');
var _keyCode2 = _interopRequireDefault(_keyCode);
var _keyCallbacks = require('../../utilities/key-callbacks');
var _keyCallbacks2 = _interopRequireDefault(_keyCallbacks);
var _checkProps = require('./check-props');
var _checkProps2 = _interopRequireDefault(_checkProps);
var _constants = require('../../utilities/constants');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /* Copyright (c) 2015-present, salesforce.com, inc. All rights reserved */
/* Licensed under BSD 3-Clause - see LICENSE.txt or git.io/sfdc-license */
var currentOpenDropdown = void 0;
var propTypes = {
/**
* **Assistive text for accessibility**
* This object is merged with the default props object on every render.
* * `label`: This is used as a visually hidden label if, no `labels.label` is provided.
* * `optionSelectedInMenu`: Added before selected menu items in Read-only variants (Picklists). The default is `Current Selection:`.
* * `removeSingleSelectedOption`: Used by inline-listbox, single-select variant to remove the selected item (pill). This is a button with focus. The default is `Remove selected option`.
* * `removePill`: Used by multiple selection Comboboxes to remove a selected item (pill). Focus is on the pill. This is not a button. The default is `, Press delete or backspace to remove`.
* * `selectedListboxLabel`: This is a label for the selected listbox. The grouping of pills for multiple selection Comboboxes. The default is `Selected Options:`.
* _Tested with snapshot testing._
*/
assistiveText: (0, _airbnbPropTypes.shape)({
label: _propTypes2.default.string,
optionSelectedInMenu: _propTypes2.default.string,
removeSingleSelectedOption: _propTypes2.default.string,
removePill: _propTypes2.default.string,
selectedListboxLabel: _propTypes2.default.string
}),
/**
* CSS classes to be added to tag with `.slds-combobox`. Uses `classNames` [API](https://github.com/JedWatson/classnames). _Tested with snapshot testing._
*/
className: _propTypes2.default.oneOfType([_propTypes2.default.array, _propTypes2.default.object, _propTypes2.default.string]),
/**
* CSS classes to be added to top level tag with `.slds-form-element` and not on `.slds-combobox_container`. Uses `classNames` [API](https://github.com/JedWatson/classnames). _Tested with snapshot testing._
*/
classNameContainer: _propTypes2.default.oneOfType([_propTypes2.default.array, _propTypes2.default.object, _propTypes2.default.string]),
/**
* CSS classes to be added to tag with `.slds-dropdown`. Uses `classNames` [API](https://github.com/JedWatson/classnames). Autocomplete/bass variant menu height should not scroll and should be determined by number items which should be no more than 10. _Tested with snapshot testing._
*/
classNameMenu: _propTypes2.default.oneOfType([_propTypes2.default.array, _propTypes2.default.object, _propTypes2.default.string]),
/**
* Event Callbacks
* * `onBlur`: Called when `input` removes focus.
* * `onChange`: Called when keyboard events occur within `input`
* * `onClose`: Triggered when the menu has closed.
* * `onFocus`: Called when `input` receives focus.
* * `onOpen`: Triggered when the menu has opened.
* * `onRequestClose`: Function called when the menu would like to hide. Please use with `isOpen`.
* * `onRequestOpen`: Function called when the menu would like to show. Please use with `isOpen`.
* * `onRequestRemoveSelectedOption`: Function called when a single selection option is to be removed.
* * `onSelect`: Function called when a menu item is selected
* * `onSubmit`: Function called when user presses enter or submits the `input`
* _Tested with Mocha testing._
*/
events: (0, _airbnbPropTypes.shape)({
onBlur: _propTypes2.default.func,
onChange: _propTypes2.default.func,
onClose: _propTypes2.default.func,
onFocus: _propTypes2.default.func,
onOpen: _propTypes2.default.func,
onRequestClose: _propTypes2.default.func,
onRequestOpen: _propTypes2.default.func,
onRequestRemoveSelectedOption: _propTypes2.default.func,
onSelect: _propTypes2.default.func,
onSubmit: _propTypes2.default.func
}),
/**
* By default, dialogs will flip their alignment (such as bottom to top) if they extend beyond a boundary element such as a scrolling parent or a window/viewpoint. This is the opposite of "flippable."
*/
hasStaticAlignment: _propTypes2.default.bool,
/**
* HTML id for component. _Tested with snapshot testing._
*/
id: _propTypes2.default.string,
/**
* **Text labels for internationalization**
* This object is merged with the default props object on every render.
* * `label`: This label appears above the input.
* * `multipleOptionsSelected`: This label is used by the readonly variant when multiple options are selected. The default is `${props.selection.length} options selected`. This will override the entire string.
* * `noOptionsFound`: Custom message that renders when no matches found. The default empty state is just text that says, 'No matches found.'.
* * `placeholder`: Input placeholder
* * `placeholderReadOnly`: Placeholder for Picklist-like Combobox
* * `removePillTitle`: Title on `X` icon
* _Tested with snapshot testing._
*/
labels: (0, _airbnbPropTypes.shape)({
label: _propTypes2.default.oneOfType([_propTypes2.default.node, _propTypes2.default.string]),
multipleOptionsSelected: _propTypes2.default.string,
noOptionsFound: _propTypes2.default.oneOfType([_propTypes2.default.node, _propTypes2.default.string]),
placeholder: _propTypes2.default.string,
placeholderReadOnly: _propTypes2.default.string,
removePillTitle: _propTypes2.default.string
}),
/**
* Forces the dropdown to be open or closed. See controlled/uncontrolled callback/prop pattern for more on suggested use view [Concepts and Best Practices](https://github.com/salesforce-ux/design-system-react/blob/master/CONTRIBUTING.md#concepts-and-best-practices) _Tested with snapshot testing._
*/
isOpen: _propTypes2.default.bool,
/**
* Accepts a custom menu item rendering function that becomes a custom component. The checkmark is still rendered in readonly variants. This function is passed the following props:
* * `assistiveText`: Object, `assistiveText` prop that is passed into Combobox
* * `option`: Object, option data for item being rendered that is passed into Combobox
* * `selected`: Boolean, allows rendering of `assistiveText.optionSelectedInMenu` in Readonly Combobox
*
* _Tested with snapshot testing._
*/
menuItem: _propTypes2.default.func,
/**
* Please select one of the following:
* * `absolute` - (default) The dialog will use `position: absolute` and style attributes to position itself. This allows inverted placement or flipping of the dialog.
* * `overflowBoundaryElement` - The dialog will overflow scrolling parents. Use on elements that are aligned to the left or right of their target and don't care about the target being within a scrolling parent. Typically this is a popover or tooltip. Dropdown menus can usually open up and down if no room exists. In order to achieve this a portal element will be created and attached to `body`. This element will render into that detached render tree.
* * `relative` - No styling or portals will be used. Menus will be positioned relative to their triggers. This is a great choice for HTML snapshot testing.
*/
menuPosition: _propTypes2.default.oneOf(['absolute', 'overflowBoundaryElement', 'relative']),
/**
* Allows multiple selections _Tested with mocha testing._
*/
multiple: _propTypes2.default.bool,
/**
* Item added to the dropdown menu. _Tested with snapshot testing._
*/
options: _propTypes2.default.array.isRequired,
/**
* Determines the height of the menu based on SLDS CSS classes. This only applies to the readonly variant. This is a `number`.
*/
readOnlyMenuItemVisibleLength: _propTypes2.default.oneOf([5, 7, 10]),
/**
* Limits auto-complete input submission to one of the provided options. _Tested with mocha testing._
*/
predefinedOptionsOnly: _propTypes2.default.bool,
/**
* Accepts an array of item objects. For single selection, pass in an array of one object. _Tested with snapshot testing._
*/
selection: _propTypes2.default.array,
/**
* Value of input. This is a controlled component, so you will need to control the input value. _Tested with snapshot testing._
*/
value: _propTypes2.default.string,
/**
* Changes styles of the input. Currently `entity` is not supported. _Tested with snapshot testing._
*/
variant: _propTypes2.default.oneOf(['base', 'inline-listbox', 'readonly'])
};
var defaultProps = {
assistiveText: {
optionSelectedInMenu: 'Current Selection:',
removeSingleSelectedOption: 'Remove selected option',
removePill: ', Press delete or backspace to remove',
selectedListboxLabel: 'Selected Options:'
},
events: {},
labels: {
noOptionsFound: 'No matches found.',
placeholderReadOnly: 'Select an Option',
removePillTitle: 'Remove'
},
menuPosition: 'absolute',
readOnlyMenuItemVisibleLength: 5,
selection: [],
variant: 'base'
};
/**
* A widget that provides a user with an input field that is either an autocomplete or readonly, accompanied with a listbox of pre-definfined options.
*/
var Combobox = function (_React$Component) {
_inherits(Combobox, _React$Component);
function Combobox(props) {
_classCallCheck(this, Combobox);
var _this = _possibleConstructorReturn(this, (Combobox.__proto__ || Object.getPrototypeOf(Combobox)).call(this, props));
_initialiseProps.call(_this);
_this.state = {
isOpen: false,
activeOption: undefined,
activeOptionIndex: -1,
// seeding initial state with this.props.selection[0]
activeSelectedOption: _this.props.selection && _this.props.selection[0] || undefined,
activeSelectedOptionIndex: 0
};
return _this;
}
/**
* Lifecycle methods
*/
_createClass(Combobox, [{
key: 'componentWillMount',
value: function componentWillMount() {
// `checkProps` issues warnings to developers about properties (similar to React's built in development tools)
(0, _checkProps2.default)(_constants.COMBOBOX, this.props);
this.generatedId = _shortid2.default.generate();
}
}, {
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(nextProps) {
var _this2 = this;
// This logic will maintain the active highlight even when the
// option order changes. One example would be the server pushes
// data out as the user has the menu open. This logic clears
// `activeOption` if the active option is no longer in the options
// list. If it's in the options list, then find the new index and
// set `activeOptionIndex`
if (!(0, _lodash8.default)(this.props.options, nextProps.options)) {
var index = nextProps.options.findIndex(function (item) {
return (0, _lodash8.default)(item, _this2.state.activeOption);
});
if (index !== -1) {
this.setState({ activeOptionIndex: index });
} else {
this.setState({ activeOption: undefined, activeOptionIndex: -1 });
}
}
// there may be issues with tabindex/focus if the app removes an item
// from selection while the user is using the listbox
var selectedOptionsRenderIsInitialRender = this.props.selection && this.props.selection.length === 0 && nextProps.selection.length > 0;
if (selectedOptionsRenderIsInitialRender) {
this.setState({
activeSelectedOption: nextProps.selection[0],
activeSelectedOptionIndex: 0
});
}
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
if (currentOpenDropdown === this) {
currentOpenDropdown = undefined;
}
}
/**
* Shared class property getter methods
*/
/**
* Menu open/close and sub-render methods
*/
}, {
key: 'getDialog',
value: function getDialog(_ref) {
var menuRenderer = _ref.menuRenderer;
// FOR BACKWARDS COMPATIBILITY
var menuPosition = this.props.isInline ? 'relative' : this.props.menuPosition; // eslint-disable-line react/prop-types
return !this.props.disabled && this.getIsOpen() ? _react2.default.createElement(
_dialog2.default,
{
align: 'bottom left',
context: this.context,
hasStaticAlignment: this.props.hasStaticAlignment,
inheritTargetWidth: true,
onClose: this.handleClose,
onOpen: this.handleOpen,
onRequestTargetElement: this.getTargetElement,
position: menuPosition,
containerProps: {
id: this.getId() + '-listbox',
role: 'listbox'
}
},
menuRenderer
) : null;
}
/**
* Input and menu keyboard event methods
*/
/**
* Selected options with listbox of pills event methods
*/
/**
* Combobox variant subrenders
* (these can probably be broken into function components
* if state is passed in as a prop)
*/
}, {
key: 'render',
value: function render() {
var props = this.props;
// Merge objects of strings with their default object
var assistiveText = (0, _lodash2.default)({}, defaultProps.assistiveText, props.assistiveText);
var labels = (0, _lodash2.default)({}, defaultProps.labels, this.props.labels);
var subRenderParameters = { assistiveText: assistiveText, labels: labels, props: this.props };
var multipleOrSingle = this.props.multiple ? 'multiple' : 'single';
var subRenders = {
base: {
multiple: this.renderBase, // same
single: this.renderBase
},
'inline-listbox': {
multiple: this.renderInlineMultiple,
single: this.renderInlineSingle
},
readonly: {
multiple: this.renderReadOnlyMultiple,
single: this.renderReadOnlySingle
}
};
var variantExists = subRenders[this.props.variant][multipleOrSingle];
return _react2.default.createElement(
'div',
{
className: (0, _classnames2.default)('slds-form-element', props.classNameContainer)
},
_react2.default.createElement(_label2.default, {
assistiveText: this.props.assistiveText.label,
htmlFor: this.getId(),
label: labels.label
}),
variantExists ? subRenders[this.props.variant][multipleOrSingle](subRenderParameters) : subRenders.base.multiple(subRenderParameters)
);
}
}]);
return Combobox;
}(_react2.default.Component);
/* eslint-enable jsx-a11y/role-supports-aria-props */
var _initialiseProps = function _initialiseProps() {
var _this3 = this;
this.getId = function () {
return _this3.props.id || _this3.generatedId;
};
this.getIsOpen = function () {
return !!((0, _lodash10.default)(_this3.props.isOpen) ? _this3.props.isOpen : _this3.state.isOpen);
};
this.getIsActiveOption = function () {
return _this3.state.activeOption && _this3.state.activeOptionIndex !== -1;
};
this.getNewActiveOptionIndex = function (_ref2) {
var activeOptionIndex = _ref2.activeOptionIndex,
offset = _ref2.offset,
options = _ref2.options;
// used by menu listbox and selected options listbox
var newIndex = activeOptionIndex + offset;
var hasNewIndex = options.length > newIndex && newIndex >= 0;
return hasNewIndex ? newIndex : activeOptionIndex;
};
this.getTargetElement = function () {
return _this3.inputRef;
};
this.isSelected = function (_ref3) {
var selection = _ref3.selection,
option = _ref3.option;
return !!(0, _lodash4.default)(selection, option);
};
this.handleClickOutside = function () {
_this3.handleRequestClose();
};
this.handleRequestClose = function () {
if (_this3.props.events.onRequestClose) {
_this3.props.events.onRequestClose();
}
if (_this3.getIsOpen()) {
_this3.setState({ isOpen: false });
}
};
this.openDialog = function () {
if (_this3.props.events.onRequestOpen) {
_this3.props.events.onRequestOpen();
} else {
_this3.setState({
isOpen: true
});
}
};
this.handleClose = function () {
var isOpen = _this3.getIsOpen();
if (isOpen) {
if (currentOpenDropdown === _this3) {
currentOpenDropdown = undefined;
}
_this3.setState({
activeOption: undefined,
activeOptionIndex: -1,
isOpen: false
});
if (_this3.props.events.onClose) {
_this3.props.events.onClose();
}
}
};
this.handleOpen = function () {
var isOpen = _this3.getIsOpen();
if (!isOpen) {
if (currentOpenDropdown && (0, _lodash12.default)(currentOpenDropdown.handleClose)) {
currentOpenDropdown.handleClose();
}
currentOpenDropdown = _this3;
_this3.setState({
isOpen: true
});
if (_this3.props.events.onOpen) {
_this3.props.events.onOpen();
}
}
};
this.requestOpenMenu = function () {
var isInlineSingleSelectionAndIsNotSelected = !_this3.props.multiple && _this3.props.selection.length === 0 && _this3.props.variant === 'inline-listbox';
if (isInlineSingleSelectionAndIsNotSelected || _this3.props.multiple || _this3.props.variant === 'readonly') {
_this3.openDialog();
}
};
this.renderMenu = function (_ref4) {
var assistiveText = _ref4.assistiveText,
labels = _ref4.labels;
var menuVariant = {
base: 'icon-title-subtitle',
'inline-listbox': 'icon-title-subtitle',
readonly: 'checkbox'
};
return _react2.default.createElement(_menu2.default, {
assistiveText: assistiveText,
activeOption: _this3.state.activeOption,
activeOptionIndex: _this3.state.activeOptionIndex,
classNameMenu: _this3.props.classNameMenu,
inputId: _this3.getId(),
inputValue: _this3.props.value,
isSelected: _this3.isSelected,
itemVisibleLength: _this3.props.variant === 'readonly' ? _this3.props.readOnlyMenuItemVisibleLength : null,
labels: labels,
menuItem: _this3.props.menuItem,
options: _this3.props.options,
onSelect: _this3.handleSelect,
clearActiveOption: _this3.clearActiveOption,
selection: _this3.props.selection,
variant: menuVariant[_this3.props.variant]
});
};
this.handleKeyDown = function (event) {
var _callbacks;
// Helper function that takes an object literal of callbacks that are triggered with a key event
(0, _keyCallbacks2.default)(event, {
callbacks: (_callbacks = {}, _defineProperty(_callbacks, _keyCode2.default.DOWN, { callback: _this3.handleKeyDownDown }), _defineProperty(_callbacks, _keyCode2.default.ENTER, { callback: _this3.handleInputSubmit }), _defineProperty(_callbacks, _keyCode2.default.ESCAPE, { callback: _this3.handleClose }), _defineProperty(_callbacks, _keyCode2.default.UP, { callback: _this3.handleKeyDownUp }), _callbacks)
});
};
this.handleNavigateListboxMenu = function (event, _ref5) {
var direction = _ref5.direction;
var offsets = { next: 1, previous: -1 };
// takes current/previous state and returns an object with the new state
_this3.setState(function (prevState) {
var newIndex = _this3.getNewActiveOptionIndex({
activeOptionIndex: prevState.activeOptionIndex,
offset: offsets[direction],
options: _this3.props.options
});
return {
activeOption: _this3.props.options[newIndex],
activeOptionIndex: newIndex
};
});
};
this.handleSelect = function (event, _ref6) {
var selection = _ref6.selection,
option = _ref6.option;
var newSelection = void 0;
var isSelected = _this3.isSelected({ selection: selection, option: option });
var singleSelectAndSelectedWasNotClicked = !_this3.props.multiple && !isSelected;
var multiSelectAndSelectedWasNotClicked = _this3.props.multiple && !isSelected;
if (singleSelectAndSelectedWasNotClicked) {
newSelection = [option];
} else if (multiSelectAndSelectedWasNotClicked) {
newSelection = [].concat(_toConsumableArray(_this3.props.selection), [option]);
} else {
newSelection = (0, _lodash6.default)(_this3.props.selection, option);
}
if (_this3.props.events.onSelect) {
_this3.props.events.onSelect(event, { selection: newSelection });
}
_this3.handleClose();
if (_this3.inputRef) {
_this3.inputRef.focus();
}
};
this.handleKeyDownDown = function (event) {
// Don't open if user is selecting text
if (!event.shiftKey) {
_this3.openDialog();
}
_this3.handleNavigateListboxMenu(event, { direction: 'next' });
};
this.handleKeyDownUp = function (event) {
// Don't open if user is selecting text
if (!event.shiftKey && _this3.state.isOpen) {
_this3.handleNavigateListboxMenu(event, { direction: 'previous' });
}
};
this.handleInputSubmit = function (event) {
// use menu options
if (_this3.getIsActiveOption()) {
_this3.handleSelect(event, {
option: _this3.state.activeOption,
selection: _this3.props.selection
});
// use input value, if not limited to predefined options (in the menu)
} else if (!_this3.props.predefinedOptionsOnly && event.target.value !== '' && _this3.props.events.onSubmit) {
_this3.props.events.onSubmit(event, {
value: event.target.value
});
}
};
this.handleInputChange = function (event) {
_this3.requestOpenMenu();
_this3.props.events.onChange(event, { value: event.target.value });
};
this.handleInputFocus = function (event) {
if (_this3.props.events.onFocus) {
_this3.props.events.onFocus(event);
}
};
this.handleInputBlur = function (event) {
// If menu is open when the input's onBlur event fires, it will close before the onClick of the menu item can fire.
setTimeout(function () {
_this3.handleClose();
}, 200);
if (_this3.props.events.onBlur) {
_this3.props.events.onBlur(event);
}
};
this.handleRemoveSelectedOption = function (event, _ref7) {
var option = _ref7.option,
index = _ref7.index;
event.preventDefault();
var onlyOnePillAndInputExists = _this3.props.selection.length === 1;
var isReadOnlyAndTwoPillsExists = _this3.props.selection.length === 2 && _this3.props.variant === 'readonly' && _this3.props.multiple;
var lastPillWasRemoved = index + 1 === _this3.props.selection.length;
if ((onlyOnePillAndInputExists || isReadOnlyAndTwoPillsExists) && _this3.inputRef) {
_this3.inputRef.focus();
} else if (lastPillWasRemoved) {
// set focus to previous option and index
_this3.setState({
activeSelectedOption: _this3.props.selection[index - 1],
activeSelectedOptionIndex: index - 1,
listboxHasFocus: true
});
} else {
// set focus to next option, but same index
_this3.setState({
activeSelectedOption: _this3.props.selection[index + 1],
activeSelectedOptionIndex: index,
listboxHasFocus: true
});
}
if (_this3.props.events.onRequestRemoveSelectedOption) {
_this3.props.events.onRequestRemoveSelectedOption(event, {
selection: (0, _lodash6.default)(_this3.props.selection, option)
});
}
};
this.handlePillClickListboxOfPills = function (event, _ref8) {
var option = _ref8.option,
index = _ref8.index;
// this is clicking the span, not the remove button
_this3.setState({
activeSelectedOption: option,
activeSelectedOptionIndex: index,
listboxHasFocus: true
});
};
this.handleNavigateListboxOfPills = function (event, _ref9) {
var direction = _ref9.direction;
var offsets = { next: 1, previous: -1 };
_this3.setState(function (prevState) {
var isLastOptionAndRightIsPressed = prevState.activeSelectedOptionIndex + 1 === _this3.props.selection.length && direction === 'next';
var isFirstOptionAndLeftIsPressed = prevState.activeSelectedOptionIndex === 0 && direction === 'previous';
var newState = void 0;
if (isLastOptionAndRightIsPressed) {
newState = {
activeSelectedOption: _this3.props.selection[0],
activeSelectedOptionIndex: 0,
listboxHasFocus: true
};
} else if (isFirstOptionAndLeftIsPressed) {
newState = {
activeSelectedOption: _this3.props.selection[_this3.props.selection.length - 1],
activeSelectedOptionIndex: _this3.props.selection.length - 1,
listboxHasFocus: true
};
} else {
var newIndex = _this3.getNewActiveOptionIndex({
activeOptionIndex: prevState.activeSelectedOptionIndex,
offset: offsets[direction],
options: _this3.props.selection
});
newState = {
activeSelectedOption: _this3.props.selection[newIndex],
activeSelectedOptionIndex: newIndex,
listboxHasFocus: true
};
}
return newState;
});
};
this.handleRequestFocusListboxOfPills = function (event, _ref10) {
var ref = _ref10.ref;
if (ref) {
_this3.activeSelectedOptionRef = ref;
_this3.activeSelectedOptionRef.focus();
}
};
this.handleBlurPill = function () {
_this3.setState({ listboxHasFocus: false });
};
this.setInputRef = function (component) {
_this3.inputRef = component;
// yes, this is a render triggered by a render.
// Dialog/Popper.js cannot place the menu until
// the trigger/target DOM node is mounted. This
// way `findDOMNode` is not called and parent
// DOM nodes are not queried.
if (!_this3.state.inputRendered) {
_this3.setState({ inputRendered: true });
}
};
this.renderBase = function (_ref11) {
var assistiveText = _ref11.assistiveText,
labels = _ref11.labels,
props = _ref11.props;
return _react2.default.createElement(
'div',
{ className: 'slds-form-element__control' },
_react2.default.createElement(
'div',
{ className: 'slds-combobox_container' },
_react2.default.createElement(
'div',
{
className: (0, _classnames2.default)('slds-combobox', 'slds-dropdown-trigger', 'slds-dropdown-trigger_click', 'ignore-react-onclickoutside', {
'slds-is-open': _this3.getIsOpen()
}, props.className),
'aria-expanded': _this3.getIsOpen(),
'aria-haspopup': 'listbox' // eslint-disable-line jsx-a11y/aria-proptypes
// used on menu's listbox
, 'aria-owns': _this3.getId() + '-listbox' // eslint-disable-line jsx-a11y/aria-proptypes
, role: 'combobox'
},
_react2.default.createElement(_innerInput2.default, {
'aria-autocomplete': 'list',
'aria-controls': _this3.getId() + '-listbox',
'aria-activedescendant': _this3.state.activeOption ? _this3.getId() + '-listbox-option-' + _this3.state.activeOption.id : null,
autoComplete: 'off',
className: 'slds-combobox__input',
containerProps: {
className: 'slds-combobox__form-element',
role: 'none'
},
iconRight: _react2.default.createElement(_inputIcon2.default, {
category: 'utility',
name: 'search',
title: labels.inputIconTitle
}),
id: _this3.getId(),
onFocus: _this3.handleInputFocus,
onBlur: _this3.handleInputBlur,
onKeyDown: _this3.handleKeyDown,
inputRef: _this3.setInputRef,
onClick: function onClick() {
_this3.openDialog();
},
onChange: _this3.handleInputChange,
placeholder: labels.placeholder,
readOnly: !!(props.predefinedOptionsOnly && _this3.state.activeOption),
role: 'textbox',
value: props.predefinedOptionsOnly ? _this3.state.activeOption && _this3.state.activeOption.label || props.value : props.value
}),
_this3.getDialog({
menuRenderer: _this3.renderMenu({ assistiveText: assistiveText, labels: labels })
})
)
),
_react2.default.createElement(_selectedListbox2.default, {
activeOption: _this3.state.activeSelectedOption,
activeOptionIndex: _this3.state.activeSelectedOptionIndex,
assistiveText: assistiveText,
events: {
onBlurPill: _this3.handleBlurPill,
onClickPill: _this3.handlePillClickListboxOfPills,
onRequestFocus: _this3.handleRequestFocusListboxOfPills,
onRequestFocusOnNextPill: _this3.handleNavigateListboxOfPills,
onRequestFocusOnPreviousPill: _this3.handleNavigateListboxOfPills,
onRequestRemove: _this3.handleRemoveSelectedOption
},
id: _this3.getId(),
labels: labels,
selection: props.selection,
listboxHasFocus: _this3.state.listboxHasFocus
})
);
};
this.renderInlineSingle = function (_ref12) {
var assistiveText = _ref12.assistiveText,
labels = _ref12.labels,
props = _ref12.props;
var iconLeft = props.selection[0] && props.selection[0].icon ? _react2.default.cloneElement(props.selection[0].icon, {
containerClassName: 'slds-combobox__input-entity-icon'
}) : null;
var value = props.selection[0] && props.selection[0].label ? props.selection[0].label : props.value;
/* eslint-disable jsx-a11y/role-supports-aria-props */
return _react2.default.createElement(
'div',
{ className: 'slds-form-element__control' },
_react2.default.createElement(
'div',
{
className: (0, _classnames2.default)('slds-combobox_container', {
'slds-has-inline-listbox': props.selection.length
})
},
_react2.default.createElement(
'div',
{
className: (0, _classnames2.default)('slds-combobox', 'slds-dropdown-trigger', 'slds-dropdown-trigger_click', 'ignore-react-onclickoutside', {
'slds-is-open': _this3.getIsOpen()
}, props.className),
'aria-expanded': _this3.getIsOpen(),
'aria-haspopup': 'listbox' // eslint-disable-line jsx-a11y/aria-proptypes
, role: 'combobox'
},
_react2.default.createElement(_innerInput2.default, {
'aria-autocomplete': 'list',
'aria-controls': _this3.getId() + '-listbox',
'aria-activedescendant': _this3.state.activeOption ? _this3.getId() + '-listbox-option-' + _this3.state.activeOption.id : null,
autoComplete: 'off',
className: 'slds-combobox__input',
containerProps: {
className: 'slds-combobox__form-element',
role: 'none'
},
iconRight: props.selection.length ? _react2.default.createElement(_inputIcon2.default, {
assistiveText: assistiveText.removeSingleSelectedOption,
buttonRef: function buttonRef(component) {
_this3.buttonRef = component;
},
category: 'utility',
iconPosition: 'right',
name: 'close',
onClick: function onClick(event) {
_this3.handleRemoveSelectedOption(event, {
option: props.selection[0]
});
}
}) : _react2.default.createElement(_inputIcon2.default, { category: 'utility', name: 'search' }),
iconLeft: iconLeft,
id: _this3.getId(),
onFocus: _this3.handleInputFocus,
onBlur: _this3.handleInputBlur,
onKeyDown: _this3.handleKeyDown,
inputRef: _this3.setInputRef,
onClick: function onClick() {
_this3.requestOpenMenu();
},
onChange: function onChange(event) {
if (!props.selection.length) {
_this3.handleInputChange(event);
}
},
placeholder: labels.placeholder,
readOnly: !!(props.predefinedOptionsOnly && _this3.state.activeOption) || !!props.selection.length,
role: 'textbox',
value: props.predefinedOptionsOnly ? _this3.state.activeOption && _this3.state.activeOption.label || props.value : value
}),
_this3.getDialog({
menuRenderer: _this3.renderMenu({ assistiveText: assistiveText, labels: labels })
})
)
)
);
};
this.renderInlineMultiple = function (_ref13) {
var assistiveText = _ref13.assistiveText,
labels = _ref13.labels,
props = _ref13.props;
return _react2.default.createElement(
'div',
{ className: 'slds-form-element__control' },
_react2.default.createElement(
'div',
{
className: (0, _classnames2.default)('slds-combobox_container', {
'slds-has-inline-listbox': props.selection.length
})
},
props.selection.length ? _react2.default.createElement(_selectedListbox2.default, {
activeOption: _this3.state.activeSelectedOption,
activeOptionIndex: _this3.state.activeSelectedOptionIndex,
assistiveText: assistiveText,
events: {
onBlurPill: _this3.handleBlurPill,
onClickPill: _this3.handlePillClickListboxOfPills,
onRequestFocus: _this3.handleRequestFocusListboxOfPills,
onRequestFocusOnNextPill: _this3.handleNavigateListboxOfPills,
onRequestFocusOnPreviousPill: _this3.handleNavigateListboxOfPills,
onRequestRemove: _this3.handleRemoveSelectedOption
},
id: _this3.getId(),
labels: labels,
selection: props.selection,
listboxHasFocus: _this3.state.listboxHasFocus
}) : null,
_react2.default.createElement(
'div',
{
className: (0, _classnames2.default)('slds-combobox', 'slds-dropdown-trigger', 'slds-dropdown-trigger_click', 'ignore-react-onclickoutside', {
'slds-is-open': _this3.getIsOpen()
}, props.className),
'aria-expanded': _this3.getIsOpen(),
'aria-haspopup': 'listbox' // eslint-disable-line jsx-a11y/aria-proptypes
, role: 'combobox'
},
_react2.default.createElement(_innerInput2.default, {
'aria-autocomplete': 'list',
'aria-controls': _this3.getId() + '-listbox',
'aria-activedescendant': _this3.state.activeOption ? _this3.getId() + '-listbox-option-' + _this3.state.activeOption.id : null,
autoComplete: 'off',
className: 'slds-combobox__input',
containerProps: {
'aria-expanded': _this3.getIsOpen(),
'aria-haspopup': 'listbox',
className: 'slds-combobox__form-element',
role: 'none'
},
iconRight: _react2.default.createElement(_inputIcon2.default, {
category: 'utility',
name: 'search',
title: labels.inputIconTitle
}),
id: _this3.getId(),
onFocus: _this3.handleInputFocus,
onBlur: _this3.handleInputBlur,
onKeyDown: _this3.handleKeyDown,
inputRef: _this3.setInputRef,
onClick: function onClick() {
_this3.openDialog();
},
onChange: _this3.handleInputChange,
placeholder: labels.placeholder,
readOnly: !!(props.predefinedOptionsOnly && _this3.state.activeOption),
role: 'textbox',
value: props.predefinedOptionsOnly ? _this3.state.activeOption && _this3.state.activeOption.label || props.value : props.value
}),
_this3.getDialog({
menuRenderer: _this3.renderMenu({ assistiveText: assistiveText, labels: labels })
})
)
)
);
};
this.renderReadOnlySingle = function (_ref14) {
var assistiveText = _ref14.assistiveText,
labels = _ref14.labels,
props = _ref14.props;
var value = props.selection[0] && props.selection[0].label || '';
/* eslint-disable jsx-a11y/role-supports-aria-props */
return _react2.default.createElement(
'div',
{ className: 'slds-form-element__control' },
_react2.default.createElement(
'div',
{ className: 'slds-combobox_container' },
_react2.default.createElement(
'div',
{ // aria attributes have been moved to the `div` wrapping `input` to comply with ARIA 1.1.
className: (0, _classnames2.default)('slds-combobox', 'slds-dropdown-trigger', 'slds-dropdown-trigger_click', 'ignore-react-onclickoutside', {
'slds-is-open': _this3.getIsOpen()
}, props.className),
'aria-expanded': _this3.getIsOpen(),
'aria-haspopup': 'listbox' // eslint-disable-line jsx-a11y/aria-proptypes
, role: 'combobox'
},
_react2.default.createElement(_innerInput2.default, {
'aria-autocomplete': 'list',
'aria-controls': _this3.getId() + '-listbox',
'aria-activedescendant': _this3.state.activeOption ? _this3.getId() + '-listbox-option-' + _this3.state.activeOption.id : null,
autoComplete: 'off',
className: 'slds-combobox__input',
containerProps: {
'aria-expanded': _this3.getIsOpen(),
'aria-haspopup': 'listbox',
className: 'slds-combobox__form-element',
role: 'none'
},
iconRight: _react2.default.createElement(_inputIcon2.default, { category: 'utility', name: 'down', variant: 'combobox' }),
id: _this3.getId(),
onFocus: _this3.handleInputFocus,
onBlur: _this3.handleInputBlur,
onKeyDown: _this3.handleKeyDown,
inputRef: _this3.setInputRef,
onClick: function onClick() {
_this3.requestOpenMenu();
},
onChange: function onChange(event) {
if (!props.selection.length) {
_this3.handleInputChange(event);
}
},
placeholder: labels.placeholderReadOnly,
readOnly: true,
role: 'textbox',
value: _this3.state.activeOption && _this3.state.activeOption.label || value
}),
_this3.getDialog({
menuRenderer: _this3.renderMenu({ assistiveText: assistiveText, labels: labels })
})
)
)
);
};
this.renderReadOnlyMultiple = function (_ref15) {
var assistiveText = _ref15.assistiveText,
labels = _ref15.labels,
props = _ref15.props;
var value = props.selection.length > 1 ? labels.multipleOptionsSelected || props.selection.length + ' options selected' : props.selection[0] && props.selection[0].label || '';
/* eslint-disable jsx-a11y/role-supports-aria-props */
return _react2.default.createElement(
'div',
{ className: 'slds-form-element__control' },
_react2.default.createElement(
'div',
{ className: 'slds-combobox_container' },
_react2.default.createElement(
'div',
{
className: (0, _classnames2.default)('slds-combobox', 'slds-dropdown-trigger', 'slds-dropdown-trigger_click', 'ignore-react-onclickoutside', {
'slds-is-open': _this3.getIsOpen()
}, props.className),
'aria-expanded': _this3.getIsOpen(),
'aria-haspopup': 'listbox' // eslint-disable-line jsx-a11y/aria-proptypes
, role: 'combobox'
},
_react2.default.createElement(_innerInput2.default, {
'aria-autocomplete': 'list',
'aria-controls': _this3.getId() + '-listbox',
'aria-activedescendant': _this3.state.activeOption ? _this3.getId() + '-listbox-option-' + _this3.state.activeOption.id : null,
autoComplete: 'off',
className: 'slds-combobox__input',
containerProps: {
'aria-expanded': _this3.getIsOpen(),
'aria-haspopup': 'listbox',
className: 'slds-combobox__form-element',
role: 'none'
},
iconRight: _react2.default.createElement(_inputIcon2.default, { category: 'utility', name: 'down', variant: 'combobox' }),
id: _this3.getId(),
onFocus: _this3.handleInputFocus,
onBlur: _this3.handleInputBlur,
onKeyDown: _this3.handleKeyDown,
inputRef: _this3.setInputRef,
onClick: function onClick() {
_this3.requestOpenMenu();
},
onChange: function onChange(event) {
if (!props.selection.length) {
_this3.handleInputChange(event);
}
},
placeholder: labels.placeholderReadOnly,
readOnly: true,
role: 'textbox',
value: value
}),
_this3.getDialog({
menuRenderer: _this3.renderMenu({ assistiveText: assistiveText, labels: labels })
})
)
),
_react2.default.createElement(_selectedListbox2.default, {
activeOption: _this3.state.activeSelectedOption,
activeOptionIndex: _this3.state.activeSelectedOptionIndex,
assistiveText: assistiveText,
events: {
onBlurPill: _this3.handleBlurPill,
onClickPill: _this3.handlePillClickListboxOfPills,
onRequestFocus: _this3.handleRequestFocusListboxOfPills,
onRequestFocusOnNextPill: _this3.handleNavigateListboxOfPills,
onRequestFocusOnPreviousPill: _this3.handleNavigateListboxOfPills,
onRequestRemove: _this3.handleRemoveSelectedOption
},
id: _this3.getId(),
labels: labels,
selection: props.selection,
listboxHasFocus: _this3.state.listboxHasFocus,
variant: _this3.props.variant,
renderAtSelectionLength: 2
})
);
};
};
Combobox.contextTypes = {
iconPath: _propTypes2.default.string
};
Combobox.displayName = _constants.COMBOBOX;
Combobox.propTypes = propTypes;
Combobox.defaultProps = defaultProps;
exports.default = Combobox;