@elastic/eui
Version:
Elastic UI Component Library
705 lines (696 loc) • 38.6 kB
JavaScript
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"],
_excluded2 = ["aria-label", "aria-describedby", "onChange", "defaultValue", "inputRef"],
_excluded3 = ["aria-label", "aria-describedby", "isVirtualized", "rowHeight"];
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
function _extends() { _extends = Object.assign ? Object.assign.bind() : 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 _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
function _iterableToArrayLimit(arr, i) { var _i = null == arr ? null : "undefined" != typeof Symbol && arr[Symbol.iterator] || arr["@@iterator"]; if (null != _i) { var _s, _e, _x, _r, _arr = [], _n = !0, _d = !1; try { if (_x = (_i = _i.call(arr)).next, 0 === i) { if (Object(_i) !== _i) return; _n = !1; } else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0); } catch (err) { _d = !0, _e = err; } finally { try { if (!_n && null != _i.return && (_r = _i.return(), Object(_r) !== _r)) return; } finally { if (_d) throw _e; } } return _arr; } }
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
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, _toPropertyKey(descriptor.key), descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
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 } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
/*
* 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.
*/
import React, { Component, createRef } from 'react';
import PropTypes from "prop-types";
import classNames from 'classnames';
import { EuiSelectableSearch } from './selectable_search';
import { EuiSelectableMessage } from './selectable_message';
import { EuiSelectableList } from './selectable_list';
import { EuiLoadingSpinner } from '../loading';
import { EuiSpacer } from '../spacer';
import { getMatchingOptions } from './matching_options';
import { keys, htmlIdGenerator } from '../../services';
import { EuiScreenReaderLive, EuiScreenReaderOnly } from '../accessibility';
import { EuiI18n } from '../i18n';
// The `searchable` prop has significant implications for a11y.
// When present, we effectively change from adhering
// to the ARIA `listbox` spec (https://www.w3.org/TR/wai-aria-practices-1.2/#Listbox)
// to the ARIA `combobox` spec (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
import { jsx as ___EmotionJSX } from "@emotion/react";
export var EuiSelectable = /*#__PURE__*/function (_Component) {
_inherits(EuiSelectable, _Component);
var _super = _createSuper(EuiSelectable);
function EuiSelectable(props) {
var _searchProps$onChange;
var _this;
_classCallCheck(this, EuiSelectable);
_this = _super.call(this, props);
_defineProperty(_assertThisInitialized(_this), "inputRef", null);
_defineProperty(_assertThisInitialized(_this), "containerRef", /*#__PURE__*/createRef());
_defineProperty(_assertThisInitialized(_this), "optionsListRef", /*#__PURE__*/createRef());
_defineProperty(_assertThisInitialized(_this), "preventOnFocus", false);
_defineProperty(_assertThisInitialized(_this), "rootId", void 0);
_defineProperty(_assertThisInitialized(_this), "messageContentId", void 0);
_defineProperty(_assertThisInitialized(_this), "listId", void 0);
_defineProperty(_assertThisInitialized(_this), "isFocusOnSearchOrListBox", function (target) {
var _this$optionsListRef$, _this$optionsListRef$2;
var searchHasFocus = _this.props.searchable && target === _this.inputRef;
var listBox = (_this$optionsListRef$ = _this.optionsListRef.current) === null || _this$optionsListRef$ === void 0 ? void 0 : (_this$optionsListRef$2 = _this$optionsListRef$.listBoxRef) === null || _this$optionsListRef$2 === void 0 ? void 0 : _this$optionsListRef$2.parentElement;
var listBoxContainsFocus = target instanceof Node && (listBox === null || listBox === void 0 ? void 0 : listBox.contains(target));
var listBoxHasFocus = target === listBox || listBoxContainsFocus;
return searchHasFocus || listBoxHasFocus;
});
_defineProperty(_assertThisInitialized(_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;
});
_defineProperty(_assertThisInitialized(_this), "onFocus", function (event) {
if (_this.preventOnFocus) {
_this.preventOnFocus = false;
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
});
}
});
_defineProperty(_assertThisInitialized(_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 keys.ARROW_UP:
event.preventDefault();
event.stopPropagation();
_this.incrementActiveOptionIndex(-1);
break;
case 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 keys.ENTER:
case 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 === 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 keys.ALT:
case keys.SHIFT:
case keys.CTRL:
case keys.META:
break;
default:
_this.setState({
activeOptionIndex: undefined
}, _this.onFocus);
break;
}
});
_defineProperty(_assertThisInitialized(_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
};
});
});
_defineProperty(_assertThisInitialized(_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 ? void 0 : (_this$props$searchPro2 = _this$props$searchPro.onChange) === null || _this$props$searchPro2 === void 0 ? void 0 : _this$props$searchPro2.call(_this$props$searchPro, searchValue, visibleOptions);
});
_defineProperty(_assertThisInitialized(_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
});
});
_defineProperty(_assertThisInitialized(_this), "onOptionClick", function (options, event, clickedOption) {
var _this$props = _this.props,
isPreFiltered = _this$props.isPreFiltered,
onChange = _this$props.onChange;
var searchValue = _this.state.searchValue;
var visibleOptions = getMatchingOptions(options, searchValue, isPreFiltered);
_this.setState({
visibleOptions: visibleOptions
});
if (onChange) {
onChange(options, event, clickedOption);
}
});
_defineProperty(_assertThisInitialized(_this), "scrollToItem", function (index, align) {
var _this$optionsListRef$3, _this$optionsListRef$4;
(_this$optionsListRef$3 = _this.optionsListRef.current) === null || _this$optionsListRef$3 === void 0 ? void 0 : (_this$optionsListRef$4 = _this$optionsListRef$3.listRef) === null || _this$optionsListRef$4 === void 0 ? void 0 : _this$optionsListRef$4.scrollToItem(index, align);
});
_defineProperty(_assertThisInitialized(_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) : '');
} : 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 = getMatchingOptions(_options, initialSearchValue, _isPreFiltered);
searchProps === null || searchProps === void 0 ? void 0 : (_searchProps$onChange = searchProps.onChange) === null || _searchProps$onChange === void 0 ? 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;
}
_createClass(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 ? 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,
rest = _objectWithoutProperties(_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 = _objectWithoutProperties(_ref2, _excluded2);
var _ref3 = listProps || unknownAccessibleName,
listAriaLabel = _ref3['aria-label'],
listAriaDescribedby = _ref3['aria-describedby'],
isVirtualized = _ref3.isVirtualized,
rowHeight = _ref3.rowHeight,
cleanedListProps = _objectWithoutProperties(_ref3, _excluded3);
var virtualizedProps;
if (isVirtualized === false) {
virtualizedProps = {
isVirtualized: isVirtualized
};
} else if (rowHeight != null) {
virtualizedProps = {
rowHeight: rowHeight
};
}
var classes = classNames('euiSelectable', {
'euiSelectable-fullHeight': height === 'full'
}, className);
/** Create message content that replaces the list if no options are available (yet) */
var messageContent;
if (errorMessage != null) {
messageContent = typeof errorMessage === 'string' ? ___EmotionJSX("p", null, errorMessage) : errorMessage;
} else if (isLoading) {
if (loadingMessage === undefined || typeof loadingMessage === 'string') {
messageContent = ___EmotionJSX(React.Fragment, null, ___EmotionJSX(EuiLoadingSpinner, {
size: "m"
}), ___EmotionJSX(EuiSpacer, {
size: "xs"
}), ___EmotionJSX("p", null, loadingMessage || ___EmotionJSX(EuiI18n, {
token: "euiSelectable.loadingOptions",
default: "Loading options"
})));
} else {
messageContent = /*#__PURE__*/React.cloneElement(loadingMessage, _objectSpread({
id: this.messageContentId
}, loadingMessage.props));
}
} else if (searchValue && visibleOptions.length === 0) {
if (noMatchesMessage === undefined || typeof noMatchesMessage === 'string') {
messageContent = ___EmotionJSX("p", null, noMatchesMessage || ___EmotionJSX(EuiI18n, {
token: "euiSelectable.noMatchingOptions",
default: "{searchValue} doesn't match any options",
values: {
searchValue: ___EmotionJSX("strong", null, searchValue)
}
}));
} else {
messageContent = /*#__PURE__*/React.cloneElement(noMatchesMessage, _objectSpread({
id: this.messageContentId
}, noMatchesMessage.props));
}
} else if (!options.length) {
if (emptyMessage === undefined || typeof emptyMessage === 'string') {
messageContent = ___EmotionJSX("p", null, emptyMessage || ___EmotionJSX(EuiI18n, {
token: "euiSelectable.noAvailableOptions",
default: "No options available"
}));
} else {
messageContent = /*#__PURE__*/React.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 ? ___EmotionJSX(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 = _slicedToArray(_ref4, 2),
screenReaderInstructions = _ref5[0],
placeholderName = _ref5[1];
return ___EmotionJSX(React.Fragment, null, ___EmotionJSX(EuiSelectableSearch, _extends({
"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": _this2.makeOptionId(activeOptionIndex) // the current faux-focused option
,
placeholder: placeholderName,
isPreFiltered: isPreFiltered !== null && isPreFiltered !== void 0 ? isPreFiltered : false,
inputRef: function inputRef(node) {
var _searchProps$inputRef;
_this2.inputRef = node;
searchProps === null || searchProps === void 0 ? void 0 : (_searchProps$inputRef = searchProps.inputRef) === null || _searchProps$inputRef === void 0 ? void 0 : _searchProps$inputRef.call(searchProps, node);
}
}, searchHasAccessibleName ? searchAccessibleName : {
'aria-label': placeholderName
}, cleanedSearchProps)), ___EmotionJSX(EuiScreenReaderOnly, null, ___EmotionJSX("p", {
id: listAriaDescribedbyId
}, selectableScreenReaderText, " ", screenReaderInstructions)));
}) : undefined;
var resultsLength = visibleOptions.filter(function (option) {
return !option.disabled;
}).length;
var listScreenReaderStatus = searchable && ___EmotionJSX(EuiI18n, {
token: "euiSelectable.searchResults",
default: function _default(_ref6) {
var resultsLength = _ref6.resultsLength;
return "".concat(resultsLength, " result").concat(resultsLength === 1 ? '' : 's', " available");
},
values: {
resultsLength: resultsLength
}
});
var listAriaDescribedbyId = this.rootId('instructions');
var listAccessibleName = getAccessibleName(listProps, listAriaDescribedbyId);
var listHasAccessibleName = Boolean(Object.keys(listAccessibleName).length);
var list = ___EmotionJSX(EuiI18n, {
token: "euiSelectable.placeholderName",
default: "Filter options"
}, function (placeholderName) {
return ___EmotionJSX(React.Fragment, null, searchable && ___EmotionJSX(EuiScreenReaderLive, {
isActive: messageContent != null || activeOptionIndex != null
}, messageContent || listScreenReaderStatus), messageContent ? ___EmotionJSX(EuiSelectableMessage, {
"data-test-subj": "euiSelectableMessage",
id: _this2.messageContentId,
bordered: listProps && listProps.bordered
}, messageContent) : ___EmotionJSX(EuiSelectableList, _extends({
"data-test-subj": "euiSelectableList",
key: "list",
options: options,
visibleOptions: visibleOptions,
searchValue: searchValue,
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 ___EmotionJSX("div", _extends({
ref: this.containerRef,
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;
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 = getMatchingOptions(options, (_stateUpdate$searchVa = stateUpdate.searchValue) !== null && _stateUpdate$searchVa !== void 0 ? _stateUpdate$searchVa : '', isPreFiltered);
if (activeOptionIndex != null && activeOptionIndex >= stateUpdate.visibleOptions.length) {
stateUpdate.activeOptionIndex = -1;
}
return stateUpdate;
}
}]);
return EuiSelectable;
}(Component);
_defineProperty(EuiSelectable, "defaultProps", {
options: [],
singleSelection: false,
searchable: false,
isPreFiltered: false
});
EuiSelectable.propTypes = {
className: PropTypes.string,
"aria-label": PropTypes.string,
"data-test-subj": PropTypes.string,
css: PropTypes.any,
/**
* Hooks up a search box to filter the list (boolean)
*/
searchable: PropTypes.oneOfType([PropTypes.oneOf([false]).isRequired, PropTypes.oneOf([true]).isRequired]).isRequired,
/**
* Passes props down to the `EuiFieldSearch`.
* See #EuiSelectableSearchProps
*/
searchProps: PropTypes.any,
/**
* Function that takes the `list` node and then
* the `search` node (if `searchable` is applied)
*/
children: PropTypes.func,
/**
* Array of EuiSelectableOption objects. See #EuiSelectableOptionProps
*/
options: PropTypes.arrayOf(PropTypes.shape({
/**
* Optional `boolean`.
* Set to `true` to indicate object is just a grouping label, not a selectable item
*/
isGroupLabel: PropTypes.oneOfType([PropTypes.oneOf([true]).isRequired, PropTypes.oneOf([false])]),
className: PropTypes.string,
"aria-label": PropTypes.string,
"data-test-subj": PropTypes.string,
css: PropTypes.any,
/**
* Visible label of option.
* Must be unique across items if `key` is not supplied
*/
label: PropTypes.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.string,
/**
* Must be unique across items.
* Will be used to match options instead of `label`
*/
key: PropTypes.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.oneOf(["on", "off", "mixed", undefined]),
disabled: PropTypes.bool,
/**
* Node to add between the selection icon and the label
*/
prepend: PropTypes.node,
/**
* Node to add to the far right of the item
*/
append: PropTypes.node,
ref: PropTypes.func,
/**
* Option data to pass through to the `renderOptions` element.
* Bypass `EuiSelectableItem` and avoid DOM attribute warnings.
*/
data: PropTypes.shape({})
}).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.func,
/**
* Passes back the current active option whenever the user changes the currently
* highlighted option via keyboard navigation or searching.
*/
onActiveOptionChange: PropTypes.func,
/**
* Sets the single selection policy of
* `false`: allows multiple selection
* `true`: only allows one selection
* `always`: can and must have only one selection
*/
singleSelection: PropTypes.oneOfType([PropTypes.oneOf(["always"]), PropTypes.bool.isRequired]),
/**
* Allows marking options as `checked='off'` as well as `'on'`
*/
allowExclusions: PropTypes.bool,
/**
* Show an loading indicator while you load and hook up your data
*/
isLoading: PropTypes.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.oneOfType([PropTypes.number.isRequired, PropTypes.oneOf(["full"])]),
/**
* See #EuiSelectableOptionsList
*/
listProps: PropTypes.any,
/**
* Custom render function for each option.
* Returns `(option, searchValue)`
*/
renderOption: PropTypes.func,
/**
* Customize the loading message. Pass a string to simply change the text,
* or a node to replace the whole content.
*/
loadingMessage: PropTypes.oneOfType([PropTypes.element.isRequired, PropTypes.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.oneOfType([PropTypes.element.isRequired, PropTypes.string.isRequired]),
/**
* Customize the empty message. Pass a string to simply change the text,
* or a node to replace the whole content.
*/
emptyMessage: PropTypes.oneOfType([PropTypes.element.isRequired, PropTypes.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.oneOfType([PropTypes.element.isRequired, PropTypes.string.isRequired, PropTypes.oneOf([null])]),
/**
* Control whether or not options get filtered internally or if consumer will filter
* Default: false
*/
isPreFiltered: PropTypes.bool,
/**
* 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.string
};