UNPKG

@elastic/eui

Version:

Elastic UI Component Library

397 lines (393 loc) 20.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.EuiSuperSelect = void 0; var _react = _interopRequireWildcard(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _classnames = _interopRequireDefault(require("classnames")); var _services = require("../../../services"); var _i18n = require("../../i18n"); var _accessibility = require("../../accessibility"); var _popover = require("../../popover"); var _super_select_control = require("./super_select_control"); var _super_select_item = require("./super_select_item"); var _super_select = require("./super_select.styles"); var _react2 = require("@emotion/react"); var _excluded = ["className", "options", "valueOfSelected", "placeholder", "onChange", "isOpen", "isInvalid", "itemClassName", "fullWidth", "popoverProps", "compressed"], _excluded2 = ["value", "dropdownDisplay", "inputDisplay", "disabled"]; function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } 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 _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], t.indexOf(o) >= 0 || {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; } function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (e.indexOf(n) >= 0) continue; t[n] = r[n]; } return t; } function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); } function _possibleConstructorReturn(t, e) { if (e && ("object" == _typeof(e) || "function" == typeof e)) return e; if (void 0 !== e) throw new TypeError("Derived constructors may only return object or undefined"); return _assertThisInitialized(t); } function _assertThisInitialized(e) { if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); return e; } function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } function _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function (t) { return t.__proto__ || Object.getPrototypeOf(t); }, _getPrototypeOf(t); } function _inherits(t, e) { if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function"); t.prototype = Object.create(e && e.prototype, { constructor: { value: t, writable: !0, configurable: !0 } }), Object.defineProperty(t, "prototype", { writable: !1 }), e && _setPrototypeOf(t, e); } function _setPrototypeOf(t, e) { return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function (t, e) { return t.__proto__ = e, t; }, _setPrototypeOf(t, e); } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(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. */ var ShiftDirection = /*#__PURE__*/function (ShiftDirection) { ShiftDirection["BACK"] = "back"; ShiftDirection["FORWARD"] = "forward"; return ShiftDirection; }(ShiftDirection || {}); var EuiSuperSelect = exports.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__*/(0, _react.createRef)()); _defineProperty(_this, "describedById", (0, _services.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 === _services.keys.ARROW_UP || event.key === _services.keys.ARROW_DOWN || event.key === _services.keys.SPACE) { event.preventDefault(); event.stopPropagation(); _this.openPopover(); } }); _defineProperty(_this, "onItemKeyDown", function (event) { switch (event.key) { case _services.keys.ESCAPE: // close the popover and prevent ancestors from handling event.preventDefault(); event.stopPropagation(); _this.closePopover(); break; case _services.keys.TAB: // Mimic native `<select>` behavior, which selects an item on tab press event.preventDefault(); event.stopPropagation(); event.target.click(); break; case _services.keys.ARROW_UP: event.preventDefault(); event.stopPropagation(); _this.shiftFocus(ShiftDirection.BACK); break; case _services.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 = (0, _classnames.default)('euiSuperSelect', popoverProps === null || popoverProps === void 0 ? void 0 : popoverProps.className); var button = (0, _react2.jsx)(_super_select_control.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 (0, _react2.jsx)(_super_select_item.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 (0, _react2.jsx)(_services.RenderWithEuiStylesMemoizer, null, function (stylesMemoizer) { var styles = stylesMemoizer(_super_select.euiSuperSelectStyles); return (0, _react2.jsx)(_popover.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 }), (0, _react2.jsx)(_accessibility.EuiScreenReaderOnly, null, (0, _react2.jsx)("p", { id: _this2.describedById }, (0, _react2.jsx)(_i18n.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." }))), (0, _react2.jsx)(_i18n.EuiI18n, { token: "euiSuperSelect.ariaLabel", default: "Select listbox" }, function (ariaLabel) { return (0, _react2.jsx)("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); })); }); } }]); }(_react.Component); _defineProperty(EuiSuperSelect, "defaultProps", { fullWidth: false, compressed: false, isInvalid: false, isLoading: false }); EuiSuperSelect.propTypes = { className: _propTypes.default.string, "aria-label": _propTypes.default.string, "data-test-subj": _propTypes.default.string, css: _propTypes.default.any, buttonRef: _propTypes.default.any, /** * @default false */ compressed: _propTypes.default.bool, /** * Expand to fill 100% of the parent. * Defaults to `fullWidth` prop of `<EuiForm>`. * @default false */ fullWidth: _propTypes.default.bool, /** * @default false */ isInvalid: _propTypes.default.bool, /** * @default false */ isLoading: _propTypes.default.bool, readOnly: _propTypes.default.bool, name: _propTypes.default.string, /** * Placeholder to display when the current selected value is empty. */ placeholder: _propTypes.default.oneOfType([_propTypes.default.node, _propTypes.default.node]), /** * Creates an input group with element(s) coming before input. * `string` | `ReactElement` or an array of these */ prepend: _propTypes.default.oneOfType([_propTypes.default.oneOfType([_propTypes.default.string.isRequired, _propTypes.default.element.isRequired]).isRequired, _propTypes.default.arrayOf(_propTypes.default.oneOfType([_propTypes.default.string.isRequired, _propTypes.default.element.isRequired]).isRequired).isRequired]), /** * Creates an input group with element(s) coming after input. * `string` | `ReactElement` or an array of these */ append: _propTypes.default.oneOfType([_propTypes.default.oneOfType([_propTypes.default.string.isRequired, _propTypes.default.element.isRequired]).isRequired, _propTypes.default.arrayOf(_propTypes.default.oneOfType([_propTypes.default.string.isRequired, _propTypes.default.element.isRequired]).isRequired).isRequired]), /** * Pass an array of options that must at least include: * `value`: storing unique value of item, * `inputDisplay`: what shows inside the form input when selected * `dropdownDisplay` (optional): what shows for the item in the dropdown */ options: _propTypes.default.arrayOf(_propTypes.default.shape({ value: _propTypes.default.any.isRequired, inputDisplay: _propTypes.default.node, dropdownDisplay: _propTypes.default.node, disabled: _propTypes.default.bool, "data-test-subj": _propTypes.default.string }).isRequired).isRequired, valueOfSelected: _propTypes.default.any, /** * Classes for the context menu item */ itemClassName: _propTypes.default.string, /** * You must pass an `onChange` function to handle the update of the value */ onChange: _propTypes.default.func, onFocus: _propTypes.default.func, onBlur: _propTypes.default.func, /** * Controls whether the options are shown. Default: false */ isOpen: _propTypes.default.bool, /** * Optional props to pass to the underlying [EuiInputPopover](/#/layout/popover#popover-attached-to-input-element). * Allows fine-grained control of the popover dropdown menu, including * `repositionOnScroll` for EuiSuperSelects used within scrollable containers, * and customizing popover panel styling. * * Does not accept a nested `popoverProps.isOpen` property - use the top level * `isOpen` API instead. */ popoverProps: _propTypes.default.any };