UNPKG

grommet

Version:

The most advanced UX framework for enterprise applications.

604 lines (520 loc) 21.8 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); var _reactDom = require('react-dom'); var _classnames3 = require('classnames'); var _classnames4 = _interopRequireDefault(_classnames3); var _CSSClassnames = require('../utils/CSSClassnames'); var _CSSClassnames2 = _interopRequireDefault(_CSSClassnames); var _Props = require('../utils/Props'); var _Props2 = _interopRequireDefault(_Props); var _KeyboardAccelerators = require('../utils/KeyboardAccelerators'); var _KeyboardAccelerators2 = _interopRequireDefault(_KeyboardAccelerators); var _Drop = require('../utils/Drop'); var _Drop2 = _interopRequireDefault(_Drop); var _Button = require('./Button'); var _Button2 = _interopRequireDefault(_Button); var _CheckBox = require('./CheckBox'); var _CheckBox2 = _interopRequireDefault(_CheckBox); var _RadioButton = require('./RadioButton'); var _RadioButton2 = _interopRequireDefault(_RadioButton); var _Search = require('./Search'); var _Search2 = _interopRequireDefault(_Search); var _CaretDown = require('./icons/base/CaretDown'); var _CaretDown2 = _interopRequireDefault(_CaretDown); var _Intl = require('../utils/Intl'); var _Intl2 = _interopRequireDefault(_Intl); var _Announcer = require('../utils/Announcer'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } // (C) Copyright 2014 Hewlett Packard Enterprise Development LP var CLASS_ROOT = _CSSClassnames2.default.SELECT; var INPUT = _CSSClassnames2.default.INPUT; var Select = function (_Component) { _inherits(Select, _Component); function Select(props, context) { _classCallCheck(this, Select); var _this = _possibleConstructorReturn(this, (Select.__proto__ || Object.getPrototypeOf(Select)).call(this, props, context)); _this._onAddDrop = _this._onAddDrop.bind(_this); _this._onRemoveDrop = _this._onRemoveDrop.bind(_this); _this._onForceClose = _this._onForceClose.bind(_this); _this._onSearchChange = _this._onSearchChange.bind(_this); _this._onNextOption = _this._onNextOption.bind(_this); _this._onPreviousOption = _this._onPreviousOption.bind(_this); _this._onEnter = _this._onEnter.bind(_this); _this._stopPropagation = _this._stopPropagation.bind(_this); _this._onInputKeyDown = _this._onInputKeyDown.bind(_this); _this._announceOptions = _this._announceOptions.bind(_this); _this.state = { announceChange: false, activeOptionIndex: -1, dropActive: false, searchText: '', value: _this._normalizeValue(props, {}) }; return _this; } _createClass(Select, [{ key: 'componentWillReceiveProps', value: function componentWillReceiveProps(nextProps) { if (nextProps.hasOwnProperty('value')) { this.setState({ value: this._normalizeValue(nextProps, this.state) }); } } }, { key: 'componentDidUpdate', value: function componentDidUpdate(prevProps, prevState) { var _props = this.props, inline = _props.inline, options = _props.options; var _state = this.state, announceChange = _state.announceChange, dropActive = _state.dropActive; var intl = this.context.intl; // Set up keyboard listeners appropriate to the current state. var activeKeyboardHandlers = { up: this._onPreviousOption, down: this._onNextOption, enter: this._onEnter, left: this._stopPropagation, right: this._stopPropagation }; if (!inline) { activeKeyboardHandlers.esc = this._onForceClose; activeKeyboardHandlers.tab = this._onForceClose; } // the order here is important, need to turn off keys before turning on if (!dropActive && prevState.dropActive) { document.removeEventListener('click', this._onRemoveDrop); _KeyboardAccelerators2.default.stopListeningToKeyboard(this, activeKeyboardHandlers); if (this._drop) { this._drop.remove(); this._drop = undefined; } } if (inline && !prevProps.inline || dropActive && !prevState.dropActive) { if (!inline) { document.addEventListener('click', this._onRemoveDrop); } _KeyboardAccelerators2.default.startListeningToKeyboard(this, activeKeyboardHandlers); if (!inline) { // If this is inside a FormField, place the drop in reference to it. var control = this.inputRef; this._drop = new _Drop2.default(control, this._renderOptions(CLASS_ROOT + '__drop'), { align: { top: 'bottom', left: 'left' }, context: this.context, responsive: false // so suggestion changes don't re-align }); } if (this._searchRef) { this._searchRef.focus(); this._searchRef._inputRef.select(); } } else if (dropActive && prevState.dropActive) { this._drop.render(this._renderOptions(CLASS_ROOT + '__drop')); } if (announceChange && options) { var matchResultsMessage = _Intl2.default.getMessage(intl, 'Match Results', { count: options.length }); var navigationHelpMessage = ''; if (options.length) { navigationHelpMessage = '(' + _Intl2.default.getMessage(intl, 'Navigation Help') + ')'; } (0, _Announcer.announce)(matchResultsMessage + ' ' + navigationHelpMessage); this.setState({ announceChange: false }); } } }, { key: 'componentWillUnmount', value: function componentWillUnmount() { document.removeEventListener('click', this._onRemoveDrop); if (this._drop) { this._drop.remove(); } } }, { key: '_normalizeValue', value: function _normalizeValue(props, state) { var multiple = props.multiple, value = props.value; var normalizedValue = value; if (multiple) { if (value) { if (!Array.isArray(value)) { normalizedValue = [value]; } } else { normalizedValue = []; } } return normalizedValue; } }, { key: '_announceOptions', value: function _announceOptions(index) { var intl = this.context.intl; var labelMessage = this._renderValue(this.props.options[index]); var enterSelectMessage = _Intl2.default.getMessage(intl, 'Enter Select'); (0, _Announcer.announce)(labelMessage + ' ' + enterSelectMessage); } }, { key: '_onInputKeyDown', value: function _onInputKeyDown(event) { var up = 38; var down = 40; if (event.keyCode === up || event.keyCode === down) { // stop the input to move the cursor when options are present event.preventDefault(); } } }, { key: '_onSearchChange', value: function _onSearchChange(event) { var inline = this.props.inline; this.setState({ announceChange: true, activeOptionIndex: -1, dropActive: !inline, searchText: event.target.value }); if (this.props.onSearch) { this.props.onSearch(event); } } }, { key: '_onAddDrop', value: function _onAddDrop(event) { var _props2 = this.props, options = _props2.options, value = _props2.value; event.preventDefault(); // Get values of options, so we can highlight selected option if (options) { var optionValues = options.map(function (option) { if (option && (typeof option === 'undefined' ? 'undefined' : _typeof(option)) === 'object') { return option.value; } else { return option; } }); var activeOptionIndex = optionValues.indexOf(value); this.setState({ dropActive: true, activeOptionIndex: activeOptionIndex }); } } }, { key: '_onRemoveDrop', value: function _onRemoveDrop(event) { if (!this._searchRef || !(0, _reactDom.findDOMNode)(this._searchRef).contains(event.target)) { this.setState({ dropActive: false }); } } }, { key: '_onForceClose', value: function _onForceClose() { this.setState({ dropActive: false }); } }, { key: '_onNextOption', value: function _onNextOption(event) { event.preventDefault(); var index = this.state.activeOptionIndex; index = Math.min(index + 1, this.props.options.length - 1); this.setState({ activeOptionIndex: index }, this._announceOptions.bind(this, index)); } }, { key: '_onPreviousOption', value: function _onPreviousOption(event) { event.preventDefault(); var index = this.state.activeOptionIndex; index = Math.max(index - 1, 0); this.setState({ activeOptionIndex: index }, this._announceOptions.bind(this, index)); } }, { key: '_valueForSelectedOption', value: function _valueForSelectedOption(option) { var multiple = this.props.multiple; var value = this.state.value; var nextValue = void 0; if (multiple) { nextValue = value.slice(0); var index = void 0; for (index = 0; index < nextValue.length; index += 1) { if (this._valueEqualsOption(nextValue[index], option)) { break; } } if (index < nextValue.length) { // already existing, remove nextValue.splice(index, 1); } else { // not there, add nextValue.push(option); } } else { nextValue = option; } return nextValue; } }, { key: '_onEnter', value: function _onEnter(event) { var _this2 = this; var _props3 = this.props, onChange = _props3.onChange, options = _props3.options; var activeOptionIndex = this.state.activeOptionIndex; var intl = this.context.intl; if (activeOptionIndex >= 0) { event.preventDefault(); // prevent submitting forms var option = options[activeOptionIndex]; var value = this._valueForSelectedOption(option); this.setState({ dropActive: false, value: value }, function () { var optionMessage = _this2._renderLabel(option); var selectedMessage = _Intl2.default.getMessage(intl, 'Selected'); (0, _Announcer.announce)(optionMessage + ' ' + selectedMessage); }); if (onChange) { onChange({ target: this.inputRef, option: option, value: value }); } } else { this.setState({ dropActive: false }); } } }, { key: '_stopPropagation', value: function _stopPropagation() { if ((0, _reactDom.findDOMNode)(this._searchRef).contains(document.activeElement)) { return true; } } }, { key: '_onClickOption', value: function _onClickOption(option) { var onChange = this.props.onChange; var value = this._valueForSelectedOption(option); this.setState({ dropActive: false, value: value }); if (onChange) { onChange({ target: this.inputRef, option: option, value: value }); } } }, { key: '_renderLabel', value: function _renderLabel(option) { if (option && (typeof option === 'undefined' ? 'undefined' : _typeof(option)) === 'object') { // revert for announce as label is often a complex object return option.label || option.value || ''; } else { return undefined === option || null === option ? '' : option; } } }, { key: '_renderValue', value: function _renderValue(option) { var intl = this.context.intl; if (Array.isArray(option)) { // Could be an Array when !inline+multiple if (1 === option.length) { return this._renderValue(option[0]); } else if (option.length > 1) { var selectedMultiple = _Intl2.default.getMessage(intl, 'Selected Multiple', { count: option.length }); return selectedMultiple; } } else if (option && (typeof option === 'undefined' ? 'undefined' : _typeof(option)) === 'object') { return option.label || option.value || ''; } else { return undefined === option || null === option ? '' : option; } } }, { key: '_valueEqualsOption', value: function _valueEqualsOption(value, option) { var result = false; if (value && (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object') { if (option && (typeof option === 'undefined' ? 'undefined' : _typeof(option)) === 'object') { result = value.value === option.value; } else { result = value.value === option; } } else { if (option && (typeof option === 'undefined' ? 'undefined' : _typeof(option)) === 'object') { result = value === option.value; } else { result = value === option; } } return result; } }, { key: '_optionSelected', value: function _optionSelected(option, value) { var _this3 = this; var result = false; if (value && Array.isArray(value)) { result = value.some(function (val) { return _this3._valueEqualsOption(val, option); }); } else { result = this._valueEqualsOption(value, option); } return result; } }, { key: '_renderOptions', value: function _renderOptions(className) { var _this4 = this; var restProps = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var intl = this.context.intl; var _props4 = this.props, id = _props4.id, inline = _props4.inline, multiple = _props4.multiple, options = _props4.options, onSearch = _props4.onSearch, value = _props4.value, _props4$searchPlaceHo = _props4.searchPlaceHolder, searchPlaceHolder = _props4$searchPlaceHo === undefined ? _Intl2.default.getMessage(intl, 'Search') : _props4$searchPlaceHo; var _state2 = this.state, activeOptionIndex = _state2.activeOptionIndex, searchText = _state2.searchText; var search = void 0; if (onSearch) { search = _react2.default.createElement(_Search2.default, { className: CLASS_ROOT + '__search', ref: function ref(_ref) { return _this4._searchRef = _ref; }, inline: true, fill: true, responsive: false, pad: 'medium', placeHolder: searchPlaceHolder, value: searchText, onDOMChange: this._onSearchChange, onKeyDown: this._onInputKeyDown }); } var items = void 0; if (options) { items = options.map(function (option, index) { var _classnames; var selected = _this4._optionSelected(option, value); var classes = (0, _classnames4.default)((_classnames = {}, _defineProperty(_classnames, CLASS_ROOT + '__option', true), _defineProperty(_classnames, CLASS_ROOT + '__option--selected', selected), _defineProperty(_classnames, CLASS_ROOT + '__option--active', index === activeOptionIndex), _classnames)); var content = _this4._renderLabel(option); if (option && option.icon) { content = _react2.default.createElement( 'span', null, option.icon, ' ', content ); } var itemOnClick = void 0; if (inline) { var itemId = '' + (option ? option.value || option : index); var Type = multiple ? _CheckBox2.default : _RadioButton2.default; content = _react2.default.createElement(Type, { key: itemId, id: id ? id + '-' + itemId : undefined, label: content, checked: selected, onChange: _this4._onClickOption.bind(_this4, option) }); } else { itemOnClick = function itemOnClick(e) { e.stopPropagation(); _this4._onClickOption.bind(_this4, option)(); }; } return _react2.default.createElement( 'li', { key: index, className: classes, onClick: itemOnClick }, content ); }); } var onClick = void 0; if (!inline) { onClick = this._onRemoveDrop; } return _react2.default.createElement( 'div', _extends({}, restProps, { className: className }), search, _react2.default.createElement( 'ol', { className: CLASS_ROOT + '__options', onClick: onClick }, items ) ); } }, { key: 'render', value: function render() { var _classnames2, _this5 = this; var _props5 = this.props, className = _props5.className, inline = _props5.inline, placeHolder = _props5.placeHolder, value = _props5.value; var active = this.state.active; var intl = this.context.intl; var classes = (0, _classnames4.default)(CLASS_ROOT, (_classnames2 = {}, _defineProperty(_classnames2, CLASS_ROOT + '--active', active), _defineProperty(_classnames2, CLASS_ROOT + '--inline', inline), _classnames2), className); var restProps = _Props2.default.omit(this.props, Object.keys(Select.propTypes)); if (inline) { return this._renderOptions(classes, restProps); } else { var renderedValue = this._renderValue(value); var shouldRenderElement = _react2.default.isValidElement(renderedValue); return _react2.default.createElement( 'div', { className: classes, onClick: this._onAddDrop }, shouldRenderElement && renderedValue, _react2.default.createElement('input', _extends({}, restProps, { ref: function ref(_ref2) { return _this5.inputRef = _ref2; }, type: shouldRenderElement ? 'hidden' : 'text', className: INPUT + ' ' + CLASS_ROOT + '__input', placeholder: placeHolder, readOnly: true, value: renderedValue || '' })), _react2.default.createElement(_Button2.default, { className: CLASS_ROOT + '__control', a11yTitle: _Intl2.default.getMessage(intl, 'Select Icon'), icon: _react2.default.createElement(_CaretDown2.default, null), onClick: this._onAddDrop }) ); } } }]); return Select; }(_react.Component); Select.displayName = 'Select'; exports.default = Select; var valueType = _propTypes2.default.oneOfType([_propTypes2.default.shape({ label: _propTypes2.default.node, value: _propTypes2.default.any }), _propTypes2.default.string, _propTypes2.default.number]); Select.propTypes = { inline: _propTypes2.default.bool, multiple: _propTypes2.default.bool, onSearch: _propTypes2.default.func, onChange: _propTypes2.default.func, // (value(s)) placeHolder: _propTypes2.default.string, searchPlaceHolder: _propTypes2.default.string, options: _propTypes2.default.arrayOf(valueType).isRequired, value: _propTypes2.default.oneOfType([valueType, _propTypes2.default.arrayOf(valueType)]) }; Select.contextTypes = { intl: _propTypes2.default.object }; module.exports = exports['default'];