@elastic/eui
Version:
Elastic UI Component Library
771 lines (763 loc) • 37.8 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _typeof = require("@babel/runtime/helpers/typeof");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.EuiSelectable = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _react = _interopRequireWildcard(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _classnames = _interopRequireDefault(require("classnames"));
var _services = require("../../services");
var _loading = require("../loading");
var _spacer = require("../spacer");
var _accessibility = require("../accessibility");
var _i18n = require("../i18n");
var _selectable_search = require("./selectable_search");
var _selectable_message = require("./selectable_message");
var _selectable_list = require("./selectable_list");
var _matching_options = require("./matching_options");
var _selectable = require("./selectable.styles");
var _react2 = require("@emotion/react");
var _excluded = ["children", "className", "options", "onChange", "onActiveOptionChange", "searchable", "searchProps", "singleSelection", "isLoading", "listProps", "renderOption", "height", "allowExclusions", "aria-label", "aria-describedby", "loadingMessage", "noMatchesMessage", "emptyMessage", "errorMessage", "selectableScreenReaderText", "isPreFiltered", "optionMatcher"],
_excluded2 = ["aria-label", "aria-describedby", "onChange", "defaultValue", "inputRef"],
_excluded3 = ["aria-label", "aria-describedby", "isVirtualized", "rowHeight"];
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _callSuper(t, o, e) { return o = (0, _getPrototypeOf2.default)(o), (0, _possibleConstructorReturn2.default)(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], (0, _getPrototypeOf2.default)(t).constructor) : o.apply(t, e)); }
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } /*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/ /**
* The `searchable` prop has significant implications for a11y. When present, we effectively change from adhering to the
* - ARIA `listbox` spec (@see https://www.w3.org/TR/wai-aria-practices-1.2/#Listbox)
* - ARIA `combobox` spec (@see https://www.w3.org/TR/wai-aria-practices-1.2/#combobox)
*
* and (re)implement all relevant attributes and keyboard interactions.
* Take note of logic that relies on `searchable` to ensure that any modifications remain in alignment.
*
* `searchProps` can only be specified when `searchable` is `true`.
*/
var EuiSelectable = exports.EuiSelectable = /*#__PURE__*/function (_Component) {
function EuiSelectable(props) {
var _searchProps$onChange;
var _this;
(0, _classCallCheck2.default)(this, EuiSelectable);
_this = _callSuper(this, EuiSelectable, [props]);
(0, _defineProperty2.default)(_this, "inputRef", null);
(0, _defineProperty2.default)(_this, "containerRef", /*#__PURE__*/(0, _react.createRef)());
(0, _defineProperty2.default)(_this, "optionsListRef", /*#__PURE__*/(0, _react.createRef)());
(0, _defineProperty2.default)(_this, "preventOnFocus", false);
(0, _defineProperty2.default)(_this, "rootId", void 0);
(0, _defineProperty2.default)(_this, "messageContentId", void 0);
(0, _defineProperty2.default)(_this, "listId", void 0);
(0, _defineProperty2.default)(_this, "isFocusOnSearchOrListBox", function (target) {
var _this$optionsListRef$;
var searchHasFocus = _this.props.searchable && target === _this.inputRef;
var listBox = (_this$optionsListRef$ = _this.optionsListRef.current) === null || _this$optionsListRef$ === void 0 || (_this$optionsListRef$ = _this$optionsListRef$.listBoxRef) === null || _this$optionsListRef$ === void 0 ? void 0 : _this$optionsListRef$.parentElement;
var listBoxContainsFocus = target instanceof Node && (listBox === null || listBox === void 0 ? void 0 : listBox.contains(target));
var listBoxHasFocus = target === listBox || listBoxContainsFocus;
return searchHasFocus || listBoxHasFocus;
});
(0, _defineProperty2.default)(_this, "onMouseDown", function () {
// Bypass onFocus when a click event originates from this.containerRef.
// Prevents onFocus from scrolling away from a clicked option and negating the selection event.
// https://github.com/elastic/eui/issues/4147
_this.preventOnFocus = true;
});
(0, _defineProperty2.default)(_this, "onFocus", function (event) {
if (_this.preventOnFocus) {
_this.preventOnFocus = false;
return;
}
if (event && _this.props.searchable && event.target === _this.inputRef) {
_this.setState({
isFocused: true
});
return;
}
if (!_this.state.visibleOptions.length || _this.state.activeOptionIndex != null) {
return;
}
if (event && !_this.isFocusOnSearchOrListBox(event.target)) {
return;
}
var firstSelected = _this.state.visibleOptions.findIndex(function (option) {
return option.checked && !option.disabled && !option.isGroupLabel;
});
if (firstSelected > -1) {
_this.setState({
activeOptionIndex: firstSelected,
isFocused: true
});
} else {
_this.setState({
activeOptionIndex: _this.state.visibleOptions.findIndex(function (option) {
return !option.disabled && !option.isGroupLabel;
}),
isFocused: true
});
}
});
(0, _defineProperty2.default)(_this, "onKeyDown", function (event) {
var optionsList = _this.optionsListRef.current;
// Check if the user is interacting with something other than the
// searchbox or selection list. If so, the user may be attempting to
// interact with the search clear button or a totally custom button,
// and listbox keyboard navigation/selection should not be triggered.
if (!_this.isFocusOnSearchOrListBox(event.target)) {
_this.setState({
activeOptionIndex: undefined,
isFocused: false
});
return;
}
switch (event.key) {
case _services.keys.ARROW_UP:
event.preventDefault();
event.stopPropagation();
_this.incrementActiveOptionIndex(-1);
break;
case _services.keys.ARROW_DOWN:
event.preventDefault();
event.stopPropagation();
_this.incrementActiveOptionIndex(1);
break;
// For non-searchable instances, SPACE interaction should align with
// the user expectation of selection toggling (e.g., input[type=checkbox]).
// ENTER is also a valid selection mechanism in this case.
case _services.keys.ENTER:
case _services.keys.SPACE:
if (_this.props.searchable) {
// For searchable instances, SPACE is reserved as a character for filtering
// via the input box, and as such only ENTER will toggle selection.
if (event.target === _this.inputRef && event.key === _services.keys.SPACE) {
return;
}
}
event.preventDefault();
event.stopPropagation();
if (_this.state.activeOptionIndex != null && optionsList) {
event.persist(); // NOTE: This is needed for React v16 backwards compatibility
optionsList.onAddOrRemoveOption(_this.state.visibleOptions[_this.state.activeOptionIndex], event);
}
break;
case _services.keys.ALT:
case _services.keys.SHIFT:
case _services.keys.CTRL:
case _services.keys.META:
break;
default:
_this.setState({
activeOptionIndex: undefined
}, _this.onFocus);
break;
}
});
(0, _defineProperty2.default)(_this, "incrementActiveOptionIndex", function (amount) {
// If there are no options available, do nothing.
if (!_this.state.visibleOptions.length) {
return;
}
_this.setState(function (_ref) {
var activeOptionIndex = _ref.activeOptionIndex,
visibleOptions = _ref.visibleOptions;
var nextActiveOptionIndex;
if (activeOptionIndex == null) {
// If this is the beginning of the user's keyboard navigation of the menu, then we'll focus
// either the first or last item.
nextActiveOptionIndex = amount < 0 ? visibleOptions.length - 1 : 0;
} else {
nextActiveOptionIndex = activeOptionIndex + amount;
if (nextActiveOptionIndex < 0) {
nextActiveOptionIndex = visibleOptions.length - 1;
} else if (nextActiveOptionIndex === visibleOptions.length) {
nextActiveOptionIndex = 0;
}
}
// Group titles and disabled options are included in option list but are not selectable
var direction = amount > 0 ? 1 : -1;
while (visibleOptions[nextActiveOptionIndex].isGroupLabel || visibleOptions[nextActiveOptionIndex].disabled) {
nextActiveOptionIndex = nextActiveOptionIndex + direction;
if (nextActiveOptionIndex < 0) {
nextActiveOptionIndex = visibleOptions.length - 1;
} else if (nextActiveOptionIndex === visibleOptions.length) {
nextActiveOptionIndex = 0;
}
}
return {
activeOptionIndex: nextActiveOptionIndex
};
});
});
(0, _defineProperty2.default)(_this, "onSearchChange", function (searchValue, visibleOptions) {
var _this$props$searchPro, _this$props$searchPro2;
_this.setState({
searchValue: searchValue,
visibleOptions: visibleOptions,
activeOptionIndex: undefined
}, function () {
if (_this.state.isFocused) {
_this.onFocus();
}
});
(_this$props$searchPro = _this.props.searchProps) === null || _this$props$searchPro === void 0 || (_this$props$searchPro2 = _this$props$searchPro.onChange) === null || _this$props$searchPro2 === void 0 || _this$props$searchPro2.call(_this$props$searchPro, searchValue, visibleOptions);
});
(0, _defineProperty2.default)(_this, "onContainerBlur", function (e) {
// Ignore blur events when moving from search to option to avoid activeOptionIndex conflicts
if (_this.isFocusOnSearchOrListBox(e.relatedTarget)) {
return;
}
_this.setState({
activeOptionIndex: undefined,
isFocused: false
});
});
(0, _defineProperty2.default)(_this, "onOptionClick", function (options, event, clickedOption) {
var _this$props = _this.props,
isPreFiltered = _this$props.isPreFiltered,
onChange = _this$props.onChange,
optionMatcher = _this$props.optionMatcher;
var searchValue = _this.state.searchValue;
var visibleOptions = (0, _matching_options.getMatchingOptions)({
options: options,
searchValue: searchValue !== null && searchValue !== void 0 ? searchValue : '',
isPreFiltered: !!isPreFiltered,
selectedOptions: [],
optionMatcher: optionMatcher
});
_this.setState({
visibleOptions: visibleOptions
});
if (onChange) {
onChange(options, event, clickedOption);
}
});
(0, _defineProperty2.default)(_this, "scrollToItem", function (index, align) {
var _this$optionsListRef$2;
(_this$optionsListRef$2 = _this.optionsListRef.current) === null || _this$optionsListRef$2 === void 0 || (_this$optionsListRef$2 = _this$optionsListRef$2.listRef) === null || _this$optionsListRef$2 === void 0 || _this$optionsListRef$2.scrollToItem(index, align);
});
(0, _defineProperty2.default)(_this, "makeOptionId", function (index) {
return index != null ? "".concat(_this.listId, "_option-").concat(index) : '';
});
_this.rootId = props.id ? function (suffix) {
return "".concat(props.id).concat(suffix ? "_".concat(suffix) : '');
} : (0, _services.htmlIdGenerator)();
_this.listId = _this.rootId('listbox');
_this.messageContentId = _this.rootId('messageContent');
var _options = props.options,
singleSelection = props.singleSelection,
_isPreFiltered = props.isPreFiltered,
searchProps = props.searchProps;
var initialSearchValue = (searchProps === null || searchProps === void 0 ? void 0 : searchProps.value) || String((searchProps === null || searchProps === void 0 ? void 0 : searchProps.defaultValue) || '');
var _visibleOptions = (0, _matching_options.getMatchingOptions)({
options: _options,
searchValue: initialSearchValue,
isPreFiltered: !!_isPreFiltered,
selectedOptions: [],
optionMatcher: props.optionMatcher
});
searchProps === null || searchProps === void 0 || (_searchProps$onChange = searchProps.onChange) === null || _searchProps$onChange === void 0 || _searchProps$onChange.call(searchProps, initialSearchValue, _visibleOptions);
// ensure that the currently selected single option is active if it is in the visibleOptions
var selectedOptions = _options.filter(function (option) {
return option.checked;
});
var _activeOptionIndex;
if (singleSelection && selectedOptions.length === 1) {
if (_visibleOptions.includes(selectedOptions[0])) {
_activeOptionIndex = _visibleOptions.indexOf(selectedOptions[0]);
}
}
_this.state = {
activeOptionIndex: _activeOptionIndex,
searchValue: initialSearchValue,
visibleOptions: _visibleOptions,
isFocused: false
};
return _this;
}
(0, _inherits2.default)(EuiSelectable, _Component);
return (0, _createClass2.default)(EuiSelectable, [{
key: "componentDidUpdate",
value: function componentDidUpdate(prevProps, prevState) {
if (prevState.activeOptionIndex !== this.state.activeOptionIndex) {
var _this$props$onActiveO, _this$props2;
var activeOption = this.state.activeOptionIndex != null ? this.state.visibleOptions[this.state.activeOptionIndex] : null;
(_this$props$onActiveO = (_this$props2 = this.props).onActiveOptionChange) === null || _this$props$onActiveO === void 0 || _this$props$onActiveO.call(_this$props2, activeOption);
}
}
}, {
key: "render",
value: function render() {
var _this2 = this;
var _this$props3 = this.props,
children = _this$props3.children,
className = _this$props3.className,
options = _this$props3.options,
onChange = _this$props3.onChange,
onActiveOptionChange = _this$props3.onActiveOptionChange,
searchable = _this$props3.searchable,
searchProps = _this$props3.searchProps,
singleSelection = _this$props3.singleSelection,
isLoading = _this$props3.isLoading,
listProps = _this$props3.listProps,
renderOption = _this$props3.renderOption,
height = _this$props3.height,
allowExclusions = _this$props3.allowExclusions,
ariaLabel = _this$props3['aria-label'],
ariaDescribedby = _this$props3['aria-describedby'],
loadingMessage = _this$props3.loadingMessage,
noMatchesMessage = _this$props3.noMatchesMessage,
emptyMessage = _this$props3.emptyMessage,
errorMessage = _this$props3.errorMessage,
selectableScreenReaderText = _this$props3.selectableScreenReaderText,
isPreFiltered = _this$props3.isPreFiltered,
optionMatcher = _this$props3.optionMatcher,
rest = (0, _objectWithoutProperties2.default)(_this$props3, _excluded);
var _this$state = this.state,
searchValue = _this$state.searchValue,
visibleOptions = _this$state.visibleOptions,
activeOptionIndex = _this$state.activeOptionIndex;
// Some messy destructuring here to remove aria-label/describedby from searchProps and listProps
// Made messier by some TS requirements
// The aria attributes are then used in getAccessibleName() to place them where they need to go
var unknownAccessibleName = {
'aria-label': undefined,
'aria-describedby': undefined
};
var _ref2 = searchProps || unknownAccessibleName,
searchAriaLabel = _ref2['aria-label'],
searchAriaDescribedby = _ref2['aria-describedby'],
propsOnChange = _ref2.onChange,
defaultValue = _ref2.defaultValue,
inputRef = _ref2.inputRef,
cleanedSearchProps = (0, _objectWithoutProperties2.default)(_ref2, _excluded2);
var _ref3 = listProps || unknownAccessibleName,
listAriaLabel = _ref3['aria-label'],
listAriaDescribedby = _ref3['aria-describedby'],
isVirtualized = _ref3.isVirtualized,
rowHeight = _ref3.rowHeight,
cleanedListProps = (0, _objectWithoutProperties2.default)(_ref3, _excluded3);
var virtualizedProps;
if (isVirtualized === false) {
virtualizedProps = {
isVirtualized: isVirtualized
};
} else if (rowHeight != null) {
virtualizedProps = {
rowHeight: rowHeight
};
}
var classes = (0, _classnames.default)('euiSelectable', className);
var cssStyles = [_selectable.euiSelectableStyles.euiSelectable, height === 'full' && _selectable.euiSelectableStyles.fullHeight];
/** Create message content that replaces the list if no options are available (yet) */
var messageContent;
if (errorMessage != null) {
messageContent = typeof errorMessage === 'string' ? (0, _react2.jsx)("p", null, errorMessage) : errorMessage;
} else if (isLoading) {
if (loadingMessage === undefined || typeof loadingMessage === 'string') {
messageContent = (0, _react2.jsx)(_react.default.Fragment, null, (0, _react2.jsx)(_loading.EuiLoadingSpinner, {
size: "m"
}), (0, _react2.jsx)(_spacer.EuiSpacer, {
size: "xs"
}), (0, _react2.jsx)("p", null, loadingMessage || (0, _react2.jsx)(_i18n.EuiI18n, {
token: "euiSelectable.loadingOptions",
default: "Loading options"
})));
} else {
messageContent = /*#__PURE__*/_react.default.cloneElement(loadingMessage, _objectSpread({
id: this.messageContentId
}, loadingMessage.props));
}
} else if (searchValue && visibleOptions.length === 0) {
if (noMatchesMessage === undefined || typeof noMatchesMessage === 'string') {
messageContent = (0, _react2.jsx)("p", null, noMatchesMessage || (0, _react2.jsx)(_i18n.EuiI18n, {
token: "euiSelectable.noMatchingOptions",
default: "{searchValue} doesn't match any options",
values: {
searchValue: (0, _react2.jsx)("strong", null, searchValue)
}
}));
} else {
messageContent = /*#__PURE__*/_react.default.cloneElement(noMatchesMessage, _objectSpread({
id: this.messageContentId
}, noMatchesMessage.props));
}
} else if (!options.length) {
if (emptyMessage === undefined || typeof emptyMessage === 'string') {
messageContent = (0, _react2.jsx)("p", null, emptyMessage || (0, _react2.jsx)(_i18n.EuiI18n, {
token: "euiSelectable.noAvailableOptions",
default: "No options available"
}));
} else {
messageContent = /*#__PURE__*/_react.default.cloneElement(emptyMessage, _objectSpread({
id: this.messageContentId
}, emptyMessage.props));
}
}
/**
* There are lots of ways to add an accessible name
* Usually we want the same name for the input and the listbox (which is added by aria-label/describedby)
* But you can always override it using searchProps or listProps
* This finds the correct name to use
*
* TODO: This doesn't handle being labelled (<label for="idOfInput">)
*/
var getAccessibleName = function getAccessibleName(props, messageContentId) {
if (props && props['aria-label']) {
return {
'aria-label': props['aria-label']
};
}
var messageContentIdString = messageContentId ? " ".concat(messageContentId) : '';
if (props && props['aria-describedby']) {
return {
'aria-describedby': "".concat(props['aria-describedby']).concat(messageContentIdString)
};
}
if (ariaLabel) {
return {
'aria-label': ariaLabel
};
}
if (ariaDescribedby) {
return {
'aria-describedby': "".concat(ariaDescribedby).concat(messageContentIdString)
};
}
return {};
};
var searchAccessibleName = getAccessibleName(searchProps, this.messageContentId);
var searchHasAccessibleName = Boolean(Object.keys(searchAccessibleName).length);
var search = searchable ? (0, _react2.jsx)(_i18n.EuiI18n, {
tokens: ['euiSelectable.screenReaderInstructions', 'euiSelectable.placeholderName'],
defaults: ['Use the Up and Down arrow keys to move focus over options. Press Enter to select. Press Escape to collapse options.', 'Filter options']
}, function (_ref4) {
var _ref5 = (0, _slicedToArray2.default)(_ref4, 2),
screenReaderInstructions = _ref5[0],
placeholderName = _ref5[1];
return (0, _react2.jsx)(_react.default.Fragment, null, (0, _react2.jsx)(_selectable_search.EuiSelectableSearch, (0, _extends2.default)({
"aria-describedby": listAriaDescribedbyId,
key: "listSearch",
options: options,
value: searchValue,
onChange: _this2.onSearchChange,
listId: _this2.optionsListRef.current ? _this2.listId : undefined // Only pass the listId if it exists on the page
,
"aria-activedescendant": activeOptionIndex != null ? _this2.makeOptionId(activeOptionIndex) : undefined,
placeholder: placeholderName,
isPreFiltered: !!isPreFiltered,
optionMatcher: optionMatcher,
inputRef: function inputRef(node) {
var _searchProps$inputRef;
_this2.inputRef = node;
searchProps === null || searchProps === void 0 || (_searchProps$inputRef = searchProps.inputRef) === null || _searchProps$inputRef === void 0 || _searchProps$inputRef.call(searchProps, node);
}
}, searchHasAccessibleName ? searchAccessibleName : {
'aria-label': placeholderName
}, cleanedSearchProps)), (0, _react2.jsx)(_accessibility.EuiScreenReaderOnly, null, (0, _react2.jsx)("p", {
id: listAriaDescribedbyId
}, selectableScreenReaderText, " ", screenReaderInstructions)));
}) : undefined;
var resultsLength = visibleOptions.filter(function (option) {
return !option.disabled;
}).length;
var listAriaDescribedbyId = this.rootId('instructions');
var listAccessibleName = getAccessibleName(listProps, listAriaDescribedbyId);
var listHasAccessibleName = Boolean(Object.keys(listAccessibleName).length);
var list = (0, _react2.jsx)(_i18n.EuiI18n, {
token: "euiSelectable.placeholderName",
default: "Filter options"
}, function (placeholderName) {
return (0, _react2.jsx)(_react.default.Fragment, null, searchable && (0, _react2.jsx)(_i18n.EuiI18n, {
token: "euiSelectable.searchResults",
default: function _default(_ref6) {
var resultsLength = _ref6.resultsLength;
return "".concat(resultsLength, " result").concat(resultsLength === 1 ? '' : 's', " available");
},
values: {
resultsLength: resultsLength
}
}, function (searchResults) {
return (0, _react2.jsx)(_accessibility.EuiScreenReaderLive, {
isActive: messageContent != null || activeOptionIndex != null
}, messageContent || searchResults);
}), messageContent ? (0, _react2.jsx)(_selectable_message.EuiSelectableMessage, {
"data-test-subj": "euiSelectableMessage",
id: _this2.messageContentId,
bordered: listProps && listProps.bordered
}, messageContent) : (0, _react2.jsx)(_selectable_list.EuiSelectableList, (0, _extends2.default)({
"data-test-subj": "euiSelectableList",
key: "list",
options: options,
visibleOptions: visibleOptions,
searchValue: searchValue,
isPreFiltered: isPreFiltered,
activeOptionIndex: activeOptionIndex,
setActiveOptionIndex: function setActiveOptionIndex(index, cb) {
_this2.setState({
activeOptionIndex: index
}, cb);
},
onOptionClick: _this2.onOptionClick,
singleSelection: singleSelection,
ref: _this2.optionsListRef,
renderOption: renderOption,
height: height,
allowExclusions: allowExclusions,
searchable: searchable,
makeOptionId: _this2.makeOptionId,
listId: _this2.listId
}, listHasAccessibleName ? listAccessibleName : searchable && {
'aria-label': placeholderName
}, cleanedListProps, virtualizedProps)));
});
return (0, _react2.jsx)("div", (0, _extends2.default)({
ref: this.containerRef,
css: cssStyles,
className: classes,
onKeyDown: this.onKeyDown,
onBlur: this.onContainerBlur,
onFocus: this.onFocus,
onMouseDown: this.onMouseDown
}, rest), children && children(list, search));
}
}], [{
key: "getDerivedStateFromProps",
value: function getDerivedStateFromProps(nextProps, prevState) {
var _stateUpdate$searchVa;
var options = nextProps.options,
isPreFiltered = nextProps.isPreFiltered,
searchProps = nextProps.searchProps,
optionMatcher = nextProps.optionMatcher;
var activeOptionIndex = prevState.activeOptionIndex,
searchValue = prevState.searchValue;
var stateUpdate = {
searchValue: searchValue,
activeOptionIndex: activeOptionIndex
};
if ((searchProps === null || searchProps === void 0 ? void 0 : searchProps.value) != null && searchProps.value !== searchValue) {
stateUpdate.searchValue = searchProps.value;
}
stateUpdate.visibleOptions = (0, _matching_options.getMatchingOptions)({
options: options,
searchValue: (_stateUpdate$searchVa = stateUpdate.searchValue) !== null && _stateUpdate$searchVa !== void 0 ? _stateUpdate$searchVa : '',
isPreFiltered: !!isPreFiltered,
selectedOptions: [],
optionMatcher: optionMatcher
});
if (activeOptionIndex != null && activeOptionIndex >= stateUpdate.visibleOptions.length) {
stateUpdate.activeOptionIndex = -1;
}
return stateUpdate;
}
}]);
}(_react.Component);
(0, _defineProperty2.default)(EuiSelectable, "defaultProps", {
options: [],
singleSelection: false,
searchable: false,
isPreFiltered: false,
optionMatcher: (0, _matching_options.createPartialStringEqualityOptionMatcher)()
});
EuiSelectable.propTypes = {
className: _propTypes.default.string,
"aria-label": _propTypes.default.string,
"data-test-subj": _propTypes.default.string,
css: _propTypes.default.any,
/**
* Hooks up a search box to filter the list (boolean)
*/
searchable: _propTypes.default.oneOfType([_propTypes.default.oneOf([false]).isRequired, _propTypes.default.oneOf([true]).isRequired]).isRequired,
/**
* Passes props down to the `EuiFieldSearch`.
* {@link EuiSelectableSearchProps}
*/
searchProps: _propTypes.default.any,
/**
* Function that takes the `list` node and then
* the `search` node (if `searchable` is applied)
*/
children: _propTypes.default.func,
/**
* Array of EuiSelectableOption objects. See {@link EuiSelectableOption}
*/
options: _propTypes.default.arrayOf(_propTypes.default.shape({
/**
* Optional `boolean`.
* Set to `true` to indicate object is just a grouping label, not a selectable item
*/
isGroupLabel: _propTypes.default.oneOfType([_propTypes.default.oneOf([true]).isRequired, _propTypes.default.oneOf([false])]),
className: _propTypes.default.string,
"aria-label": _propTypes.default.string,
"data-test-subj": _propTypes.default.string,
css: _propTypes.default.any,
/**
* Visible label of option.
* Must be unique across items if `key` is not supplied
*/
label: _propTypes.default.string,
/**
* Optionally change the searchable term by passing a different string other than the `label`.
* Best used when creating a custom `optionRender` to separate the label from metadata but allowing to search on both
*/
searchableLabel: _propTypes.default.string,
/**
* Must be unique across items.
* Will be used to match options instead of `label`
*/
key: _propTypes.default.string,
/**
* Leave `undefined` to indicate not selected. Pass a string of
* 'on' to indicate inclusion, 'off' to indicate exclusion,
* or 'mixed' to indicate inclusion for some.
*/
checked: _propTypes.default.any,
disabled: _propTypes.default.bool,
/**
* Node to add between the selection icon and the label
*/
prepend: _propTypes.default.node,
/**
* Node to add to the far right of the item
*/
append: _propTypes.default.node,
ref: _propTypes.default.func,
/**
* Option data to pass through to the `renderOptions` element.
* Bypass `EuiSelectableItem` and avoid DOM attribute warnings.
*/
data: _propTypes.default.shape({}),
/**
* How to handle long text within the item.
* Wrapping only works if `isVirtualization` is false.
* @default 'truncate'
*/
textWrap: _propTypes.default.oneOf(["truncate", "wrap"]),
/**
* If textWrap is set to `truncate`, you can pass a custom truncation configuration
* that accepts any [EuiTextTruncate](/#/utilities/text-truncation) prop except for
* `text` and `children`.
*
* Note: when searching, custom truncation props are ignored. The highlighted search
* text will always take precedence.
*/
truncationProps: _propTypes.default.any,
/**
* Optional custom tooltip content for the button
*/
toolTipContent: _propTypes.default.node,
/**
* Optional props to pass to the underlying **[EuiToolTip](/#/display/tooltip)**
*/
toolTipProps: _propTypes.default.any
}).isRequired).isRequired,
/**
* Passes back the altered `options` array with selected options having `checked: 'on'`.
* Also passes back the React click/keyboard event as a second argument,
* and the option that triggered the onChange event as a third argument.
*/
onChange: _propTypes.default.func,
/**
* Passes back the current active option whenever the user changes the currently
* highlighted option via keyboard navigation or searching.
*/
onActiveOptionChange: _propTypes.default.func,
/**
* Sets the single selection policy of
* `false`: allows multiple selection
* `true`: only allows one selection
* `always`: can and must have only one selection
* @default false
*/
singleSelection: _propTypes.default.oneOfType([_propTypes.default.oneOf(["always"]), _propTypes.default.bool.isRequired]),
/**
* Allows marking options as `checked='off'` as well as `'on'`
*/
allowExclusions: _propTypes.default.bool,
/**
* Show an loading indicator while you load and hook up your data
*/
isLoading: _propTypes.default.bool,
/**
* Sets the max height in pixels or pass `full` to allow
* the whole group to fill the height of its container and
* allows the list grow as well
*/
height: _propTypes.default.oneOfType([_propTypes.default.number.isRequired, _propTypes.default.oneOf(["full"])]),
/**
* See {@link EuiSelectableOptionsListPropsWithDefaults}
*/
listProps: _propTypes.default.any,
/**
* Custom render function for each option.
* Returns `(option, searchValue)`
*/
renderOption: _propTypes.default.func,
/**
* Customize the loading message. Pass a string to simply change the text,
* or a node to replace the whole content.
*/
loadingMessage: _propTypes.default.oneOfType([_propTypes.default.element.isRequired, _propTypes.default.string.isRequired]),
/**
* Customize the no matches message. Pass a string to simply change the text,
* or a node to replace the whole content.
*/
noMatchesMessage: _propTypes.default.oneOfType([_propTypes.default.element.isRequired, _propTypes.default.string.isRequired]),
/**
* Customize the empty message. Pass a string to simply change the text,
* or a node to replace the whole content.
*/
emptyMessage: _propTypes.default.oneOfType([_propTypes.default.element.isRequired, _propTypes.default.string.isRequired]),
/**
* Add an error message.
* The message will be shown when the value is not `null` or `undefined`.
* Pass a string to simply change the text, or a node to replace the whole content.
*
* `errorMessage={hasErrors ? 'My error message' : null}`
*/
errorMessage: _propTypes.default.oneOfType([_propTypes.default.element.isRequired, _propTypes.default.string.isRequired, _propTypes.default.oneOf([null])]),
/**
* Control whether or not options get filtered internally (i.e., whether filtering is
* handled by EUI or by you, the consumer).
* If set to `true`, all passed `options` will be displayed regardless of the user's
* search input.
*
* Additionally allows passing a configuration object which enables turning off
* search highlighting if needed.
*
* @default false
*/
isPreFiltered: _propTypes.default.oneOfType([_propTypes.default.bool.isRequired, _propTypes.default.shape({
highlightSearch: _propTypes.default.bool
}).isRequired]),
/**
* Optional screen reader instructions to announce upon focus/interaction. This text is read out
* after the `EuiSelectable` label and a brief pause, but before the default keyboard instructions for
* interacting with a selectable are read out.
*/
selectableScreenReaderText: _propTypes.default.string,
/**
* Optional custom option matcher function
*
* @example
* const exactEqualityMatcher: EuiSelectableOptionMatcher = ({ option, searchValue }) => {
* return option.label === searchValue;
* }
*/
optionMatcher: _propTypes.default.func
};