@elastic/eui
Version:
Elastic UI Component Library
303 lines (299 loc) • 13 kB
JavaScript
import _extends from "@babel/runtime/helpers/extends";
import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
import _createClass from "@babel/runtime/helpers/createClass";
import _possibleConstructorReturn from "@babel/runtime/helpers/possibleConstructorReturn";
import _getPrototypeOf from "@babel/runtime/helpers/getPrototypeOf";
import _inherits from "@babel/runtime/helpers/inherits";
import _defineProperty from "@babel/runtime/helpers/defineProperty";
var _excluded = ["className", "options", "valueOfSelected", "placeholder", "onChange", "isOpen", "isInvalid", "itemClassName", "fullWidth", "popoverProps", "compressed"],
_excluded2 = ["value", "dropdownDisplay", "inputDisplay", "disabled"];
function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(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.
*/
import React, { Component, createRef } from 'react';
import classNames from 'classnames';
import { htmlIdGenerator, keys, RenderWithEuiStylesMemoizer } from '../../../services';
import { EuiI18n } from '../../i18n';
import { EuiScreenReaderOnly } from '../../accessibility';
import { EuiInputPopover } from '../../popover';
import { EuiSuperSelectControl } from './super_select_control';
import { EuiSuperSelectItem } from './super_select_item';
import { euiSuperSelectStyles } from './super_select.styles';
import { jsx as ___EmotionJSX } from "@emotion/react";
var ShiftDirection = /*#__PURE__*/function (ShiftDirection) {
ShiftDirection["BACK"] = "back";
ShiftDirection["FORWARD"] = "forward";
return ShiftDirection;
}(ShiftDirection || {});
export var EuiSuperSelect = /*#__PURE__*/function (_Component) {
function EuiSuperSelect() {
var _this;
_classCallCheck(this, EuiSuperSelect);
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
_this = _callSuper(this, EuiSuperSelect, [].concat(args));
_defineProperty(_this, "itemNodes", []);
_defineProperty(_this, "_isMounted", false);
_defineProperty(_this, "controlButtonRef", /*#__PURE__*/createRef());
_defineProperty(_this, "describedById", htmlIdGenerator('euiSuperSelect_')('_screenreaderDescribeId'));
_defineProperty(_this, "state", {
isPopoverOpen: _this.props.isOpen || false,
currentIndex: 0
});
_defineProperty(_this, "setItemNode", function (node, index) {
_this.itemNodes[index] = node;
});
_defineProperty(_this, "openPopover", function () {
var _this$props = _this.props,
options = _this$props.options,
valueOfSelected = _this$props.valueOfSelected;
var indexOfSelected = options.findIndex(function (option) {
return (option === null || option === void 0 ? void 0 : option.value) === valueOfSelected;
});
var candidateIndex = valueOfSelected != null && indexOfSelected >= 0 ? indexOfSelected : 0;
var initialIndex = candidateIndex;
// If the item is disabled, find the first focusable item going forward
while (initialIndex < options.length && (_options$initialIndex = options[initialIndex]) !== null && _options$initialIndex !== void 0 && _options$initialIndex.disabled) {
var _options$initialIndex;
initialIndex++;
}
if (initialIndex >= options.length) {
initialIndex = candidateIndex;
}
_this.setState({
isPopoverOpen: options.length > 0,
currentIndex: initialIndex
});
requestAnimationFrame(function () {
requestAnimationFrame(function () {
if (!_this._isMounted) {
return;
}
_this.focusItemAt(initialIndex);
if (_this.props.onFocus) {
_this.props.onFocus();
}
});
});
});
_defineProperty(_this, "closePopover", function () {
_this.setState({
isPopoverOpen: false,
currentIndex: -1
});
// Refocus back to the toggling control button on popover close
requestAnimationFrame(function () {
var _this$controlButtonRe;
(_this$controlButtonRe = _this.controlButtonRef.current) === null || _this$controlButtonRe === void 0 || _this$controlButtonRe.focus();
});
if (_this.props.onBlur) {
_this.props.onBlur();
}
});
_defineProperty(_this, "itemClicked", function (value) {
_this.closePopover();
if (_this.props.onChange) {
_this.props.onChange(value);
}
});
_defineProperty(_this, "onSelectKeyDown", function (event) {
// Mimic the ways native `<select>`s can be opened via keypress
if (event.key === keys.ARROW_UP || event.key === keys.ARROW_DOWN || event.key === keys.SPACE) {
event.preventDefault();
event.stopPropagation();
_this.openPopover();
}
});
_defineProperty(_this, "onItemKeyDown", function (event) {
switch (event.key) {
case keys.ESCAPE:
// close the popover and prevent ancestors from handling
event.preventDefault();
event.stopPropagation();
_this.closePopover();
break;
case keys.TAB:
// Mimic native `<select>` behavior, which selects an item on tab press
event.preventDefault();
event.stopPropagation();
event.target.click();
break;
case keys.ARROW_UP:
event.preventDefault();
event.stopPropagation();
_this.shiftFocus(ShiftDirection.BACK);
break;
case keys.ARROW_DOWN:
event.preventDefault();
event.stopPropagation();
_this.shiftFocus(ShiftDirection.FORWARD);
break;
}
});
return _this;
}
_inherits(EuiSuperSelect, _Component);
return _createClass(EuiSuperSelect, [{
key: "componentDidMount",
value: function componentDidMount() {
this._isMounted = true;
if (this.props.isOpen) {
this.openPopover();
}
}
}, {
key: "componentWillUnmount",
value: function componentWillUnmount() {
this._isMounted = false;
}
}, {
key: "focusItemAt",
value: function focusItemAt(index) {
var _this$itemNodes$index;
(_this$itemNodes$index = this.itemNodes[index]) === null || _this$itemNodes$index === void 0 || _this$itemNodes$index.focus();
}
}, {
key: "shiftFocus",
value: function shiftFocus(direction) {
var options = this.props.options;
var currentIndex = this.state.currentIndex;
if (currentIndex === -1) {
// somehow the select options has lost focus
this.focusItemAt(0);
this.setState({
currentIndex: 0
});
return;
}
// Note: this component purposely does not cycle arrow key navigation
// to match native <select> elements
var step = direction === ShiftDirection.BACK ? -1 : 1;
var nextIndex = currentIndex + step;
while (nextIndex >= 0 && nextIndex < options.length) {
var _options$nextIndex;
if (!((_options$nextIndex = options[nextIndex]) !== null && _options$nextIndex !== void 0 && _options$nextIndex.disabled)) {
this.focusItemAt(nextIndex);
this.setState({
currentIndex: nextIndex
});
return;
}
nextIndex += step;
}
}
}, {
key: "render",
value: function render() {
var _this2 = this,
_options$this$state$c;
var _this$props2 = this.props,
className = _this$props2.className,
options = _this$props2.options,
valueOfSelected = _this$props2.valueOfSelected,
placeholder = _this$props2.placeholder,
onChange = _this$props2.onChange,
isOpen = _this$props2.isOpen,
isInvalid = _this$props2.isInvalid,
itemClassName = _this$props2.itemClassName,
fullWidth = _this$props2.fullWidth,
popoverProps = _this$props2.popoverProps,
compressed = _this$props2.compressed,
rest = _objectWithoutProperties(_this$props2, _excluded);
var popoverClasses = classNames('euiSuperSelect', popoverProps === null || popoverProps === void 0 ? void 0 : popoverProps.className);
var button = ___EmotionJSX(EuiSuperSelectControl, _extends({
options: options,
value: valueOfSelected,
placeholder: placeholder,
onClick: this.state.isPopoverOpen ? this.closePopover : this.openPopover,
onKeyDown: this.onSelectKeyDown,
className: className,
fullWidth: fullWidth,
isInvalid: isInvalid,
compressed: compressed
}, rest, {
buttonRef: this.controlButtonRef,
isDropdownOpen: this.state.isPopoverOpen
}));
var items = options.map(function (option, index) {
var value = option.value,
dropdownDisplay = option.dropdownDisplay,
inputDisplay = option.inputDisplay,
disabled = option.disabled,
optionRest = _objectWithoutProperties(option, _excluded2);
if (value == null) return;
return ___EmotionJSX(EuiSuperSelectItem, _extends({
key: index
/* NOTE: This should rather use "li" to align select-like behavior. But the current
implementation relies on the interactive and focusable item for the navigation.
This will require additional refactoring to adjust but we might want to decide first
if the effort is worth it, considering the unification plans for selection components
as part of OneSelect (https://github.com/elastic/eui/issues/8808).
*/,
element: "button",
id: String(value),
className: itemClassName,
checked: valueOfSelected === value ? 'on' : undefined,
isSelected: valueOfSelected === value,
isFocused: _this2.state.currentIndex === index,
isSingleSelection: true,
isDisabled: disabled,
textWrap: "wrap",
onClick: function onClick() {
return _this2.itemClicked(value);
},
onKeyDown: _this2.onItemKeyDown,
ref: function ref(node) {
return _this2.setItemNode(node, index);
},
"aria-selected": valueOfSelected === value
}, optionRest), dropdownDisplay || inputDisplay);
});
var ariaActiveDescendant = ((_options$this$state$c = options[this.state.currentIndex]) === null || _options$this$state$c === void 0 ? void 0 : _options$this$state$c.value) != null ? String(options[this.state.currentIndex].value) : undefined;
return ___EmotionJSX(RenderWithEuiStylesMemoizer, null, function (stylesMemoizer) {
var styles = stylesMemoizer(euiSuperSelectStyles);
return ___EmotionJSX(EuiInputPopover, _extends({
closePopover: _this2.closePopover,
panelPaddingSize: "none"
}, popoverProps, {
className: popoverClasses,
isOpen: isOpen || _this2.state.isPopoverOpen,
input: button,
fullWidth: fullWidth,
disableFocusTrap: true // This component handles its own focus manually
}), ___EmotionJSX(EuiScreenReaderOnly, null, ___EmotionJSX("p", {
id: _this2.describedById
}, ___EmotionJSX(EuiI18n, {
token: "euiSuperSelect.screenReaderAnnouncement",
default: "You are in a form selector and must select a single option. Use the Up and Down arrow keys to navigate or Escape to close."
}))), ___EmotionJSX(EuiI18n, {
token: "euiSuperSelect.ariaLabel",
default: "Select listbox"
}, function (ariaLabel) {
return ___EmotionJSX("div", {
"aria-label": ariaLabel,
"aria-describedby": _this2.describedById,
css: styles.euiSuperSelect__listbox,
className: "euiSuperSelect__listbox eui-scrollBar",
role: "listbox",
"aria-activedescendant": ariaActiveDescendant,
tabIndex: 0
}, items);
}));
});
}
}]);
}(Component);
_defineProperty(EuiSuperSelect, "defaultProps", {
fullWidth: false,
compressed: false,
isInvalid: false,
isLoading: false
});