@salesforce/design-system-react
Version:
Salesforce Lightning Design System for React
1,180 lines (1,042 loc) • 64.5 kB
JavaScript
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); }
function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a 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); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
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; }
/* eslint-disable max-lines */
/* Copyright (c) 2015-present, salesforce.com, inc. All rights reserved */
/* Licensed under BSD 3-Clause - see LICENSE.txt or git.io/sfdc-license */
/* eslint-disable jsx-a11y/role-has-required-aria-props */
/* eslint-disable max-lines */
import React from 'react';
import PropTypes from 'prop-types';
import assign from 'lodash.assign';
import find from 'lodash.find';
import reject from 'lodash.reject';
import isEqual from 'lodash.isequal';
import findIndex from 'lodash.findindex';
import isFunction from 'lodash.isfunction';
import classNames from 'classnames';
import shortid from 'shortid';
import Button from '../button';
import Dialog from '../utilities/dialog';
import InnerInput from '../../components/input/private/inner-input';
import InputIcon from '../icon/input-icon';
import Menu from './private/menu';
import Label from '../forms/private/label';
import Popover from '../popover';
import SelectedListBox from '../pill-container/private/selected-listbox';
import FieldLevelHelpTooltip from '../tooltip/private/field-level-help-tooltip';
import KEYS from '../../utilities/key-code';
import KeyBuffer from '../../utilities/key-buffer';
import keyLetterMenuItemSelect from '../../utilities/key-letter-menu-item-select';
import mapKeyEventCallbacks from '../../utilities/key-callbacks';
import menuItemSelectScroll from '../../utilities/menu-item-select-scroll';
import checkProps from './check-props';
import { COMBOBOX } from '../../utilities/constants';
import componentDoc from './docs.json';
var currentOpenDropdown;
var documentDefined = typeof document !== 'undefined';
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:`.
* * `popoverLabel`: Used by popover variant, assistive text for the Popover aria-label.
* * `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: PropTypes.shape({
label: PropTypes.string,
optionSelectedInMenu: PropTypes.string,
popoverLabel: PropTypes.string,
removeSingleSelectedOption: PropTypes.string,
removePill: PropTypes.string,
selectedListboxLabel: PropTypes.string
}),
/**
* The `aria-describedby` attribute is used to indicate the IDs of the elements that describe the object. It is used to establish a relationship between widgets or groups and text that described them.
* This is very similar to aria-labelledby: a label describes the essence of an object, while a description provides more information that the user might need. _Tested with snapshot testing._
*/
'aria-describedby': PropTypes.string,
/**
* CSS classes to be added to tag with `.slds-combobox`. Uses `classNames` [API](https://github.com/JedWatson/classnames). _Tested with snapshot testing._
*/
className: PropTypes.oneOfType([PropTypes.array, PropTypes.object, PropTypes.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: PropTypes.oneOfType([PropTypes.array, PropTypes.object, PropTypes.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: PropTypes.oneOfType([PropTypes.array, PropTypes.object, PropTypes.string]),
/**
* CSS classes to be added to tag with `.slds-dropdown__header`. Uses `classNames` [API](https://github.com/JedWatson/classnames).
*/
classNameMenuSubHeader: PropTypes.oneOfType([PropTypes.array, PropTypes.object, PropTypes.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: PropTypes.shape({
onBlur: PropTypes.func,
onChange: PropTypes.func,
onClose: PropTypes.func,
onFocus: PropTypes.func,
onOpen: PropTypes.func,
onRequestClose: PropTypes.func,
onRequestOpen: PropTypes.func,
onRequestRemoveSelectedOption: PropTypes.func,
onSelect: PropTypes.func,
onSubmit: PropTypes.func
}),
/**
* Message to display when the input is in an error state. When this is present, also visually highlights the component as in error. _Tested with snapshot testing._
*/
errorText: PropTypes.string,
/**
* A [Tooltip](https://react.lightningdesignsystem.com/components/tooltips/) component that is displayed next to the `labels.label`. The props from the component will be merged and override any default props.
*/
fieldLevelHelpTooltip: PropTypes.node,
/**
* 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. `hasStaticAlignment` disables this behavior and allows this component to extend beyond boundary elements. _Not tested._
*/
hasStaticAlignment: PropTypes.bool,
/**
* HTML id for component. _Tested with snapshot testing._
*/
id: PropTypes.string,
/**
* An [Input](https://react.lightningdesignsystem.com/components/inputs) component.
* The props from this component will override any default props.
*/
input: PropTypes.node,
/**
* **Text labels for internationalization**
* This object is merged with the default props object on every render.
* * `label`: This label appears above the input.
* * `cancelButton`: This label is only used by the dialog variant for the cancel button in the footer of the dialog. The default is `Cancel`
* * `doneButton`: This label is only used by the dialog variant for the done button in the footer of the dialog. The default is `Done`
* * `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: PropTypes.shape({
label: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
multipleOptionsSelected: PropTypes.string,
noOptionsFound: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
placeholder: PropTypes.string,
placeholderReadOnly: PropTypes.string,
removePillTitle: PropTypes.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: PropTypes.bool,
/**
* Sets the dialog width to the width of one of the following:
* * `target`: Sets the dialog width to the width of the target. (Menus attached to `input` typically follow this UX pattern),
* * `menu`: Consider setting a `menuMaxWidth` if using this value. If not, width will be set to width of largest menu item.
* * `none`: Does not set a width on the dialog. _Tested with snapshot testing._
*/
inheritWidthOf: PropTypes.oneOf(['target', 'menu', 'none']),
/**
* Accepts a custom menu item rendering function that becomes a custom component. It should return a React node. 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._
*/
onRenderMenuItem: PropTypes.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: PropTypes.oneOf(['absolute', 'overflowBoundaryElement', 'relative']),
/**
* Sets a maximum width that the menu will be used if `inheritWidthOf` is set to `menu`. (Example: 500px) _Tested with snapshot testing._
*
*/
menuMaxWidth: PropTypes.string,
/**
* Allows multiple selections _Tested with mocha testing._
*/
multiple: PropTypes.bool,
/**
* **Array of item objects in the dropdown menu.**
* Each object can contain:
* * `icon`: An `Icon` component. (not used in read-only variant)
* * `id`: A unique identifier string.
* * `label`: A primary string of text for a menu item or group separator.
* * `subTitle`: A secondary string of text added for clarity. (optional)
* * `type`: 'separator' is the only type currently used
* * `disabled`: Set to true to disable this menu item.
* * `tooltipContent`: Content that is displayed in tooltip when item is disabled
* ```
* {
* id: '2',
* label: 'Salesforce.com, Inc.',
* subTitle: 'Account • San Francisco',
* type: 'account',
* disabled: true,
* tooltipContent: "You don't have permission to select this item."
* },
* ```
* Note: At the moment, Combobox does not support two consecutive separators. _Tested with snapshot testing._
*/
options: PropTypes.arrayOf(PropTypes.PropTypes.shape({
id: PropTypes.string.isRequired,
icon: PropTypes.node,
label: PropTypes.string,
subTitle: PropTypes.string,
type: PropTypes.string,
disabled: PropTypes.boolean,
tooltipContent: PropTypes.node
})),
/**
* Determines the height of the menu based on SLDS CSS classes. This only applies to the readonly variant. This is a `number`.
*/
readOnlyMenuItemVisibleLength: PropTypes.oneOf([5, 7, 10]),
/**
* Limits auto-complete input submission to one of the provided options. _Tested with mocha testing._
*/
predefinedOptionsOnly: PropTypes.bool,
/**
* A `Popover` component. The props from this popover will be merged and override any default props. This also allows a Combobox's Popover dialog to be a controlled component. _Tested with snapshot testing._
*/
popover: PropTypes.node,
/**
* Applies label styling for a required form element. _Tested with snapshot testing._
*/
required: PropTypes.bool,
/**
* Accepts an array of item objects. For single selection, pass in an array of one object. For item object keys, see `options` prop. _Tested with snapshot testing._
*/
selection: PropTypes.arrayOf(PropTypes.PropTypes.shape({
id: PropTypes.string.isRequired,
icon: PropTypes.node,
label: PropTypes.string,
subTitle: PropTypes.string,
type: PropTypes.string
})).isRequired,
/**
* This callback exposes the selected listbox reference / DOM node to parent components.
*/
selectedListboxRef: PropTypes.func,
/**
* Disables the input and prevents editing the contents. This only applies for single readonly and inline-listbox variants.
*/
singleInputDisabled: PropTypes.bool,
/**
* Accepts a tooltip that is displayed when hovering on disabled menu items.
*/
tooltipMenuItemDisabled: PropTypes.element,
/**
* Value of input. _This is a controlled component,_ so you will need to control the input value by passing the `value` from `onChange` to a parent component or state manager, and then pass it back into the componet with this prop. Please see examples for more clarification. _Tested with snapshot testing._
*/
value: PropTypes.string,
/**
* Changes styles of the input and menu. Currently `entity` is not supported.
* The options are:
* * `base`: An autocomplete Combobox also allows a user to select an option from a list, but that list can be affected by what the user types into the input of the Combobox. The SLDS website used to call the autocomplete Combobox its `base` variant.
* * `inline-listbox`: An Entity Autocomplete Combobox or Lookup, is used to search for and select Salesforce Entities.
* * `popover`: A dialog Combobox is best used when a listbox, tree, grid, or tree-grid is not the best solution. This variant allows custom content.
* * `readonly`: A readonly text input that allows a user to select an option from a pre-defined list of options. It does not allow free form user input, nor does it allow the user to modify the selected value.
*
* _Tested with snapshot testing._
*/
variant: PropTypes.oneOf(['base', 'inline-listbox', 'popover', 'readonly'])
};
var defaultProps = {
assistiveText: {
optionSelectedInMenu: 'Current Selection:',
removeSingleSelectedOption: 'Remove selected option',
removePill: ', Press delete or backspace to remove',
selectedListboxLabel: 'Selected Options:'
},
events: {},
labels: {
cancelButton: 'Cancel',
doneButton: "Done",
noOptionsFound: 'No matches found.',
optionDisabledTooltipLabel: 'This option is disabled.',
placeholderReadOnly: 'Select an Option',
removePillTitle: 'Remove'
},
inheritWidthOf: 'target',
menuPosition: 'absolute',
readOnlyMenuItemVisibleLength: 5,
required: false,
selection: [],
singleInputDisabled: false,
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 =
/*#__PURE__*/
function (_React$Component) {
_inherits(Combobox, _React$Component);
function Combobox(_props) {
var _this;
_classCallCheck(this, Combobox);
_this = _possibleConstructorReturn(this, _getPrototypeOf(Combobox).call(this, _props));
_defineProperty(_assertThisInitialized(_this), "getCustomPopoverProps", function (body, _ref) {
var assistiveText = _ref.assistiveText,
labels = _ref.labels;
/*
* Generate the popover props based on passed in popover props. Using the default behavior if not provided by passed in popover
*/
var popoverBody = React.createElement("div", null, React.createElement("div", {
className: "slds-assistive-text",
id: "".concat(_this.getId(), "-label")
}, assistiveText.popoverLabel), body);
var popoverFooter = React.createElement("div", null, React.createElement(Button, {
label: labels.cancelButton,
onClick: function onClick(e) {
_this.handleClose(e, {
trigger: 'cancel'
});
}
}), React.createElement(Button, {
label: labels.doneButton,
variant: "brand",
onClick: _this.handleClose
}));
var defaultPopoverProps = {
ariaLabelledby: "".concat(_this.getId(), "-label"),
align: 'bottom',
body: popoverBody,
className: 'slds-popover_full-width',
footer: popoverFooter,
footerClassName: 'slds-popover__footer_form',
hasNoNubbin: true,
id: "".concat(_this.getId()),
isOpen: _this.state.isOpen,
hasNoTriggerStyles: true,
onOpen: _this.handleOpen,
onClose: _this.handleClose,
onRequestClose: _this.handleClose
};
/* Merge in passed popover's props if there is any to override the default popover props */
var popoverProps = assign(defaultPopoverProps, _this.props.popover ? _this.props.popover.props : {});
popoverProps.body = popoverBody;
delete popoverProps.children;
return popoverProps;
});
_defineProperty(_assertThisInitialized(_this), "getId", function () {
return _this.props.id || _this.generatedId;
});
_defineProperty(_assertThisInitialized(_this), "getIsActiveOption", function () {
return _this.state.activeOption && _this.state.activeOptionIndex !== -1;
});
_defineProperty(_assertThisInitialized(_this), "getIsOpen", function () {
return !!(typeof _this.props.isOpen === 'boolean' ? _this.props.isOpen : _this.state.isOpen);
});
_defineProperty(_assertThisInitialized(_this), "getNewActiveOptionIndex", function (_ref2) {
var activeOptionIndex = _ref2.activeOptionIndex,
offset = _ref2.offset,
options = _ref2.options;
// used by menu listbox and selected options listbox
var nextIndex = activeOptionIndex + offset;
var skipIndex = options.length > nextIndex && nextIndex >= 0 && options[nextIndex].type === 'separator';
var newIndex = skipIndex ? nextIndex + offset : nextIndex;
var hasNewIndex = options.length > nextIndex && nextIndex >= 0;
return hasNewIndex ? newIndex : activeOptionIndex;
});
_defineProperty(_assertThisInitialized(_this), "getTargetElement", function () {
return _this.inputRef;
});
_defineProperty(_assertThisInitialized(_this), "setInputRef", function (component) {
_this.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 (!_this.state.inputRendered) {
_this.setState({
inputRendered: true
});
}
});
_defineProperty(_assertThisInitialized(_this), "setSelectedListboxRef", function (ref) {
_this.selectedListboxRef = ref;
if (_this.props.selectedListboxRef) {
_this.props.selectedListboxRef(ref);
}
});
_defineProperty(_assertThisInitialized(_this), "handleBlurPill", function () {
_this.setState({
listboxHasFocus: false
});
});
_defineProperty(_assertThisInitialized(_this), "handleClickOutside", function (event) {
_this.handleRequestClose(event, {});
});
_defineProperty(_assertThisInitialized(_this), "handleClose", function (event) {
var _ref3 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
trigger = _ref3.trigger;
var isOpen = _this.getIsOpen();
if (isOpen) {
if (currentOpenDropdown === _assertThisInitialized(_this)) {
currentOpenDropdown = undefined;
}
_this.setState({
activeOption: undefined,
activeOptionIndex: -1,
isOpen: false
});
if (_this.props.variant === 'popover' && trigger === 'cancel') {
if (_this.props.popover.props.onClose) {
_this.props.popover.props.onClose(event, {
trigger: trigger
});
}
}
if (_this.props.events.onClose) {
_this.props.events.onClose(event, {});
}
}
});
_defineProperty(_assertThisInitialized(_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 () {
var activeElement = documentDefined ? document.activeElement : false; // detect if the scrollbar of the combobox-autocomplete/lookup menu is clicked in IE11. If it is, return focus to input, and do not close menu.
if (activeElement && activeElement.tagName === 'DIV' && activeElement.id === "".concat(_this.getId(), "-listbox")) {
if (_this.inputRef) {
_this.inputRef.focus();
}
} else if (!_this.props.popover) {
_this.handleClose(event);
}
}, 200);
if (_this.props.events.onBlur) {
_this.props.events.onBlur(event);
}
});
_defineProperty(_assertThisInitialized(_this), "handleInputChange", function (event) {
_this.requestOpenMenu();
if (_this.props.events && _this.props.events.onChange) {
_this.props.events.onChange(event, {
value: event.target.value
});
}
});
_defineProperty(_assertThisInitialized(_this), "handleInputFocus", function (event) {
if (_this.props.events.onFocus) {
_this.props.events.onFocus(event, {});
}
});
_defineProperty(_assertThisInitialized(_this), "handleInputSubmit", function (event) {
if (_this.state.activeOption && _this.state.activeOption.disabled) {
return;
} // use menu options
if (_this.getIsActiveOption()) {
_this.handleSelect(event, {
option: _this.state.activeOption,
selection: _this.props.selection
}); // use input value, if not limited to predefined options (in the menu)
} else if (!_this.props.predefinedOptionsOnly && event.target.value !== '' && _this.props.events.onSubmit) {
_this.props.events.onSubmit(event, {
value: event.target.value
});
}
});
_defineProperty(_assertThisInitialized(_this), "handleKeyDown", function (event) {
var _callbacks;
var callbacks = (_callbacks = {}, _defineProperty(_callbacks, KEYS.DOWN, {
callback: _this.handleKeyDownDown
}), _defineProperty(_callbacks, KEYS.ENTER, {
callback: _this.handleInputSubmit
}), _defineProperty(_callbacks, KEYS.ESCAPE, {
callback: _this.handleClose
}), _defineProperty(_callbacks, KEYS.UP, {
callback: _this.handleKeyDownUp
}), _callbacks);
if (_this.props.variant === 'readonly') {
if (_this.props.selection.length > 2) {
callbacks[KEYS.TAB] = {
callback: _this.handleKeyDownTab
};
} else {
callbacks[KEYS.TAB] = undefined;
}
callbacks.other = {
callback: _this.handleKeyDownOther,
stopPropagation: false
};
} // Helper function that takes an object literal of callbacks that are triggered with a key event
mapKeyEventCallbacks(event, {
callbacks: callbacks
});
});
_defineProperty(_assertThisInitialized(_this), "handleKeyDownDown", function (event) {
// Don't open if user is selecting text
if (!event.shiftKey) {
_this.openDialog();
}
if (_this.props.variant !== 'popover') {
_this.handleNavigateListboxMenu(event, {
direction: 'next'
});
}
});
_defineProperty(_assertThisInitialized(_this), "handleKeyDownTab", function () {
if (_this.selectedListboxRef) {
_this.setState({
listboxHasFocus: true
});
}
});
_defineProperty(_assertThisInitialized(_this), "handleKeyDownUp", function (event) {
// Don't open if user is selecting text
if (!event.shiftKey && _this.state.isOpen) {
_this.handleNavigateListboxMenu(event, {
direction: 'previous'
});
}
});
_defineProperty(_assertThisInitialized(_this), "handleKeyDownOther", function (event) {
var activeOptionIndex = keyLetterMenuItemSelect({
key: event.key,
keyBuffer: _this.menuKeyBuffer,
keyCode: event.keyCode,
options: _this.props.options
});
if (activeOptionIndex !== undefined) {
if (_this.state.isOpen) {
menuItemSelectScroll({
container: _this.menuRef,
focusedIndex: activeOptionIndex
});
}
_this.setState({
activeOption: _this.props.options[activeOptionIndex],
activeOptionIndex: activeOptionIndex
});
}
});
_defineProperty(_assertThisInitialized(_this), "handleNavigateListboxMenu", function (event, _ref4) {
var direction = _ref4.direction;
var offsets = {
next: 1,
previous: -1
}; // takes current/previous state and returns an object with the new state
_this.setState(function (prevState) {
var newIndex = _this.getNewActiveOptionIndex({
activeOptionIndex: prevState.activeOptionIndex,
offset: offsets[direction],
options: _this.props.options
});
if (_this.state.isOpen) {
menuItemSelectScroll({
container: _this.menuRef,
focusedIndex: newIndex
});
}
return {
activeOption: _this.props.options[newIndex],
activeOptionIndex: newIndex
};
});
});
_defineProperty(_assertThisInitialized(_this), "handleNavigateSelectedListbox", function (event, _ref5) {
var direction = _ref5.direction;
var offsets = {
next: 1,
previous: -1
};
_this.setState(function (prevState) {
var isLastOptionAndRightIsPressed = prevState.activeSelectedOptionIndex + 1 === _this.props.selection.length && direction === 'next';
var isFirstOptionAndLeftIsPressed = prevState.activeSelectedOptionIndex === 0 && direction === 'previous';
var newState;
if (isLastOptionAndRightIsPressed) {
newState = {
activeSelectedOption: _this.props.selection[0],
activeSelectedOptionIndex: 0,
listboxHasFocus: true
};
} else if (isFirstOptionAndLeftIsPressed) {
newState = {
activeSelectedOption: _this.props.selection[_this.props.selection.length - 1],
activeSelectedOptionIndex: _this.props.selection.length - 1,
listboxHasFocus: true
};
} else {
var newIndex = _this.getNewActiveOptionIndex({
activeOptionIndex: prevState.activeSelectedOptionIndex,
offset: offsets[direction],
options: _this.props.selection
});
newState = {
activeSelectedOption: _this.props.selection[newIndex],
activeSelectedOptionIndex: newIndex,
listboxHasFocus: true
};
}
return newState;
});
});
_defineProperty(_assertThisInitialized(_this), "handleOpen", function (event, data) {
var isOpen = _this.getIsOpen();
if (!isOpen) {
if (currentOpenDropdown && isFunction(currentOpenDropdown.handleClose)) {
currentOpenDropdown.handleClose();
}
} else {
currentOpenDropdown = _assertThisInitialized(_this);
_this.setState({
isOpen: true
});
if (_this.props.events.onOpen) {
_this.props.events.onOpen(event, data);
}
}
});
_defineProperty(_assertThisInitialized(_this), "handlePillClickSelectedListbox", function (event, _ref6) {
var option = _ref6.option,
index = _ref6.index;
// this is clicking the span, not the remove button
_this.setState({
activeSelectedOption: option,
activeSelectedOptionIndex: index,
listboxHasFocus: true
});
});
_defineProperty(_assertThisInitialized(_this), "handlePillFocus", function () {
if (!_this.state.listboxHasFocus) {
_this.setState({
listboxHasFocus: true
});
}
});
_defineProperty(_assertThisInitialized(_this), "handleRemoveSelectedOption", function (event, _ref7) {
var option = _ref7.option,
index = _ref7.index;
event.preventDefault();
var onlyOnePillAndInputExists = _this.props.selection.length === 1;
var isReadOnlyAndTwoPillsExists = _this.props.selection.length === 2 && _this.props.variant === 'readonly' && _this.props.multiple;
var lastPillWasRemoved = index + 1 === _this.props.selection.length;
if ((onlyOnePillAndInputExists || isReadOnlyAndTwoPillsExists) && _this.inputRef) {
_this.inputRef.focus();
} else if (lastPillWasRemoved) {
// set focus to previous option and index
_this.setState({
activeSelectedOption: _this.props.selection[index - 1],
activeSelectedOptionIndex: index - 1,
listboxHasFocus: true
});
} else {
// set focus to next option, but same index
_this.setState({
activeSelectedOption: _this.props.selection[index + 1],
activeSelectedOptionIndex: index,
listboxHasFocus: true
});
}
if (_this.props.events.onRequestRemoveSelectedOption) {
_this.props.events.onRequestRemoveSelectedOption(event, {
selection: reject(_this.props.selection, option)
});
}
});
_defineProperty(_assertThisInitialized(_this), "handleRequestClose", function (event, data) {
if (_this.props.events.onRequestClose) {
_this.props.events.onRequestClose(event, data);
}
if (_this.getIsOpen()) {
_this.setState({
isOpen: false
});
}
});
_defineProperty(_assertThisInitialized(_this), "handleRequestFocusSelectedListbox", function (event, _ref8) {
var ref = _ref8.ref;
if (ref) {
_this.activeSelectedOptionRef = ref;
_this.activeSelectedOptionRef.focus();
}
});
_defineProperty(_assertThisInitialized(_this), "handleSelect", function (event, _ref9) {
var selection = _ref9.selection,
option = _ref9.option;
var newSelection;
var isSelected = _this.isSelected({
selection: selection,
option: option
});
var singleSelectAndSelectedWasNotClicked = !_this.props.multiple && !isSelected;
var multiSelectAndSelectedWasNotClicked = _this.props.multiple && !isSelected;
if (singleSelectAndSelectedWasNotClicked) {
newSelection = [option];
} else if (multiSelectAndSelectedWasNotClicked) {
newSelection = [].concat(_toConsumableArray(_this.props.selection), [option]);
} else {
newSelection = reject(_this.props.selection, option);
}
if (_this.props.events.onSelect) {
_this.props.events.onSelect(event, {
selection: newSelection
});
}
_this.handleClose(); // if (this.inputRef) {
// this.inputRef.focus();
// }
});
_defineProperty(_assertThisInitialized(_this), "isSelected", function (_ref10) {
var selection = _ref10.selection,
option = _ref10.option;
return !!find(selection, option);
});
_defineProperty(_assertThisInitialized(_this), "openDialog", function () {
if (_this.props.events.onRequestOpen) {
_this.props.events.onRequestOpen();
} else {
_this.setState({
isOpen: true
});
}
});
_defineProperty(_assertThisInitialized(_this), "requestOpenMenu", function () {
var isInlineSingleSelectionAndIsNotSelected = !_this.props.multiple && _this.props.selection.length === 0 && _this.props.variant === 'inline-listbox';
if (isInlineSingleSelectionAndIsNotSelected || _this.props.multiple || _this.props.variant === 'readonly') {
_this.openDialog();
}
});
_defineProperty(_assertThisInitialized(_this), "renderBase", function (_ref11) {
var assistiveText = _ref11.assistiveText,
labels = _ref11.labels,
props = _ref11.props,
userDefinedProps = _ref11.userDefinedProps;
return React.createElement("div", {
className: "slds-form-element__control"
}, React.createElement("div", {
className: "slds-combobox_container"
}, React.createElement("div", {
className: classNames('slds-combobox', 'slds-dropdown-trigger', 'slds-dropdown-trigger_click', 'ignore-react-onclickoutside', {
'slds-is-open': _this.getIsOpen()
}, {
'slds-has-error': props.errorText
}, props.className),
"aria-expanded": _this.getIsOpen(),
"aria-haspopup": "listbox" // eslint-disable-line jsx-a11y/aria-proptypes
// used on menu's listbox
,
"aria-owns": "".concat(_this.getId(), "-listbox") // eslint-disable-line jsx-a11y/aria-proptypes
,
role: "combobox"
}, React.createElement(InnerInput, _extends({
"aria-autocomplete": "list",
"aria-controls": "".concat(_this.getId(), "-listbox"),
"aria-activedescendant": _this.state.activeOption ? "".concat(_this.getId(), "-listbox-option-").concat(_this.state.activeOption.id) : null,
"aria-describedby": _this.getErrorId(),
autoComplete: "off",
className: "slds-combobox__input",
containerProps: {
className: 'slds-combobox__form-element',
role: 'none'
},
iconRight: React.createElement(InputIcon, {
category: "utility",
name: "search",
title: labels.inputIconTitle
}),
id: _this.getId(),
onFocus: _this.handleInputFocus,
onBlur: _this.handleInputBlur,
onKeyDown: _this.handleKeyDown,
inputRef: _this.setInputRef,
onClick: function onClick() {
_this.openDialog();
},
onChange: _this.handleInputChange,
placeholder: labels.placeholder,
readOnly: !!(props.predefinedOptionsOnly && _this.state.activeOption),
required: props.required,
role: "textbox",
value: props.predefinedOptionsOnly ? _this.state.activeOption && _this.state.activeOption.label || props.value : props.value
}, userDefinedProps.input)), _this.getDialog({
menuRenderer: _this.renderMenu({
assistiveText: assistiveText,
labels: labels
})
}))), React.createElement(SelectedListBox, {
activeOption: _this.state.activeSelectedOption,
activeOptionIndex: _this.state.activeSelectedOptionIndex,
assistiveText: assistiveText,
events: {
onBlurPill: _this.handleBlurPill,
onClickPill: _this.handlePillClickSelectedListbox,
onPillFocus: _this.handlePillFocus,
onRequestFocus: _this.handleRequestFocusSelectedListbox,
onRequestFocusOnNextPill: _this.handleNavigateSelectedListbox,
onRequestFocusOnPreviousPill: _this.handleNavigateSelectedListbox,
onRequestRemove: _this.handleRemoveSelectedOption
},
id: "".concat(_this.getId(), "-selected-listbox"),
labels: labels,
selectedListboxRef: _this.setSelectedListboxRef,
selection: props.selection,
listboxHasFocus: _this.state.listboxHasFocus
}), props.errorText && React.createElement("div", {
className: "slds-has-error"
}, React.createElement("div", {
id: _this.getErrorId(),
className: "slds-form-element__help slds-has-error"
}, props.errorText)));
});
_defineProperty(_assertThisInitialized(_this), "renderInlineMultiple", function (_ref12) {
var assistiveText = _ref12.assistiveText,
labels = _ref12.labels,
props = _ref12.props,
userDefinedProps = _ref12.userDefinedProps;
return React.createElement("div", {
className: "slds-form-element__control"
}, React.createElement("div", {
className: classNames('slds-combobox_container', {
'slds-has-inline-listbox': props.selection.length
})
}, props.selection.length ? React.createElement(SelectedListBox, {
activeOption: _this.state.activeSelectedOption,
activeOptionIndex: _this.state.activeSelectedOptionIndex,
assistiveText: assistiveText,
events: {
onBlurPill: _this.handleBlurPill,
onClickPill: _this.handlePillClickSelectedListbox,
onPillFocus: _this.handlePillFocus,
onRequestFocus: _this.handleRequestFocusSelectedListbox,
onRequestFocusOnNextPill: _this.handleNavigateSelectedListbox,
onRequestFocusOnPreviousPill: _this.handleNavigateSelectedListbox,
onRequestRemove: _this.handleRemoveSelectedOption
},
id: "".concat(_this.getId(), "-selected-listbox"),
labels: labels,
selectedListboxRef: _this.setSelectedListboxRef,
selection: props.selection,
listboxHasFocus: _this.state.listboxHasFocus
}) : null, React.createElement("div", {
className: classNames('slds-combobox', 'slds-dropdown-trigger', 'slds-dropdown-trigger_click', 'ignore-react-onclickoutside', {
'slds-is-open': _this.getIsOpen()
}, {
'slds-has-error': props.errorText
}, props.className),
"aria-expanded": _this.getIsOpen(),
"aria-haspopup": "listbox" // eslint-disable-line jsx-a11y/aria-proptypes
,
role: "combobox"
}, React.createElement(InnerInput, _extends({
"aria-autocomplete": "list",
"aria-controls": "".concat(_this.getId(), "-listbox"),
"aria-activedescendant": _this.state.activeOption ? "".concat(_this.getId(), "-listbox-option-").concat(_this.state.activeOption.id) : null,
"aria-describedby": _this.getErrorId(),
autoComplete: "off",
className: "slds-combobox__input",
containerProps: {
'aria-expanded': _this.getIsOpen(),
'aria-haspopup': 'listbox',
className: 'slds-combobox__form-element',
role: 'none'
},
iconRight: React.createElement(InputIcon, {
category: "utility",
name: "search",
title: labels.inputIconTitle
}),
id: _this.getId(),
onFocus: _this.handleInputFocus,
onBlur: _this.handleInputBlur,
onKeyDown: _this.handleKeyDown,
inputRef: _this.setInputRef,
onClick: function onClick() {
_this.openDialog();
},
onChange: _this.handleInputChange,
placeholder: labels.placeholder,
readOnly: !!(props.predefinedOptionsOnly && _this.state.activeOption),
required: props.required,
role: "textbox",
value: props.predefinedOptionsOnly ? _this.state.activeOption && _this.state.activeOption.label || props.value : props.value
}, userDefinedProps.input)), _this.getDialog({
menuRenderer: _this.renderMenu({
assistiveText: assistiveText,
labels: labels
})
}), props.errorText && React.createElement("div", {
id: _this.getErrorId(),
className: "slds-form-element__help"
}, props.errorText))));
});
_defineProperty(_assertThisInitialized(_this), "renderInlineSingle", function (_ref13) {
var assistiveText = _ref13.assistiveText,
labels = _ref13.labels,
props = _ref13.props,
userDefinedProps = _ref13.userDefinedProps;
var iconLeft = props.selection[0] && props.selection[0].icon ? React.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 React.createElement("div", {
className: "slds-form-element__control"
}, React.createElement("div", {
className: classNames('slds-combobox_container', {
'slds-has-inline-listbox': props.selection.length
})
}, React.createElement("div", {
className: classNames('slds-combobox', 'slds-dropdown-trigger', 'slds-dropdown-trigger_click', 'ignore-react-onclickoutside', {
'slds-is-open': _this.getIsOpen()
}, {
'slds-has-error': props.errorText
}, props.className),
"aria-expanded": _this.getIsOpen(),
"aria-haspopup": "listbox" // eslint-disable-line jsx-a11y/aria-proptypes
,
role: "combobox"
}, React.createElement(InnerInput, _extends({
"aria-autocomplete": "list",
"aria-controls": "".concat(_this.getId(), "-listbox"),
"aria-activedescendant": _this.state.activeOption ? "".concat(_this.getId(), "-listbox-option-").concat(_this.state.activeOption.id) : null,
"aria-describedby": _this.getErrorId(),
autoComplete: "off",
className: "slds-combobox__input",
containerProps: {
className: 'slds-combobox__form-element',
role: 'none'
},
disabled: _this.props.singleInputDisabled,
iconRight: props.selection.length ? React.createElement(InputIcon, {
assistiveText: {
icon: assistiveText.removeSingleSelectedOption
},
buttonRef: function buttonRef(component) {
_this.buttonRef = component;
},
category: "utility",
iconPosition: "right",
name: "close",
onClick: function onClick(event) {
_this.handleRemoveSelectedOption(event, {
option: props.selection[0]
});
}
}) : React.createElement(InputIcon, {
category: "utility",
name: "search"
}),
iconLeft: iconLeft,
id: _this.getId(),
onFocus: _this.handleInputFocus,
onBlur: _this.handleInputBlur,
onKeyDown: _this.handleKeyDown,
inputRef: _this.setInputRef,
onClick: function onClick() {
_this.requestOpenMenu();
},
onChange: function onChange(event) {
if (!props.selection.length) {
_this.handleInputChange(event);
}
},
placeholder: labels.placeholder,
readOnly: !!(props.predefinedOptionsOnly && _this.state.activeOption) || !!props.selection.length,
required: props.required,
role: "textbox",
value: props.predefinedOptionsOnly ? _this.state.activeOption && _this.state.activeOption.label || props.value : value
}, userDefinedProps.input)), _this.getDialog({
menuRenderer: _this.renderMenu({
assistiveText: assistiveText,
labels: labels
})
}), props.errorText && React.createElement("div", {
id: _this.getErrorId(),
className: "slds-form-element__help"
}, props.errorText))));
});
_defineProperty(_assertThisInitialized(_this), "renderMenu", function (_ref14) {
var assistiveText = _ref14.assistiveText,
labels = _ref14.labels;
var menuVariant = {
base: 'icon-title-subtitle',
'inline-listbox': 'icon-title-subtitle',
readonly: 'checkbox'
};
return React.createElement(Menu, {
assistiveText: assistiveText,
activeOption: _this.state.activeOption,
activeOptionIndex: _this.state.activeOptionIndex,
classNameMenu: _this.props.classNameMenu,
classNameMenuSubHeader: _this.props.classNameMenuSubHeader,
tooltipMenuItemDisabled: _this.props.tooltipMenuItemDisabled,
inheritWidthOf: _this.props.inheritWidthOf,
inputId: _this.getId(),
inputValue: _this.props.value,
isSelected: _this.isSelected,
itemVisibleLength: _this.props.variant === 'readonly' ? _this.props.readOnlyMenuItemVisibleLength : null,
labels: labels // For backward compatibility, 'menuItem' prop will be deprecated soon
,
onRenderMenuItem: _this.props.onRenderMenuItem ? _this.props.onRenderMenuItem : _this.props.menuItem,
menuPosition: _this.props.menuPosition,
menuRef: function menuRef(ref) {
_this.menuRef = ref;
},
maxWidth: _this.props.menuMaxWidth,
options: _this.props.options,
onSelect: _this.handleSelect,
clearActiveOption: _this.clearActiveOption,
selection: _this.props.selection,
variant: menuVariant[_this.props.variant]
});
});
_defineProperty(_assertThisInitialized(_this), "renderPopover", function (_ref15) {
var assistiveText = _ref15.assistiveText,
labels = _ref15.labels,
props = _ref15.props;
var popoverProps = _this.getCustomPopoverProps(_this.props.popover.props.body, {
assistiveText: assistiveText,
labels: labels
});
return React.createElement("div", {
className: "slds-form-element__control"
}, React.createElement("div", {
className: "slds-combobox_container"
}, React.createElement("div", {
className: classNames('slds-combobox', 'slds-dropdown-trigger', 'slds-dropdown-trigger_click', 'ignore-react-onclickoutside', {
'slds-is-open': _this.getIsOpen()
}, {
'slds-has-error': props.errorText
}, props.className),
"aria-expanded": _this.getIsOpen(),
"aria-haspopup": "dialog" // eslint-disable-line jsx-a11y/aria-proptypes
,
"a