UNPKG

grommet

Version:

The most advanced UX framework for enterprise applications.

611 lines (518 loc) 20.4 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _keys = require('babel-runtime/core-js/object/keys'); var _keys2 = _interopRequireDefault(_keys); var _extends2 = require('babel-runtime/helpers/extends'); var _extends3 = _interopRequireDefault(_extends2); var _defineProperty2 = require('babel-runtime/helpers/defineProperty'); var _defineProperty3 = _interopRequireDefault(_defineProperty2); var _typeof2 = require('babel-runtime/helpers/typeof'); var _typeof3 = _interopRequireDefault(_typeof2); var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _createClass2 = require('babel-runtime/helpers/createClass'); var _createClass3 = _interopRequireDefault(_createClass2); var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); var _inherits2 = require('babel-runtime/helpers/inherits'); var _inherits3 = _interopRequireDefault(_inherits2); var _react = require('react'); var _react2 = _interopRequireDefault(_react); 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 _DOM = require('../utils/DOM'); 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 }; } var CLASS_ROOT = _CSSClassnames2.default.SELECT; // (C) Copyright 2014 Hewlett Packard Enterprise Development LP var INPUT = _CSSClassnames2.default.INPUT; var FORM_FIELD = _CSSClassnames2.default.FORM_FIELD; var Select = function (_Component) { (0, _inherits3.default)(Select, _Component); function Select(props, context) { (0, _classCallCheck3.default)(this, Select); var _this = (0, _possibleConstructorReturn3.default)(this, (Select.__proto__ || (0, _getPrototypeOf2.default)(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; } (0, _createClass3.default)(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 = (0, _DOM.findAncestor)(this.componentRef, FORM_FIELD) || this.componentRef; 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 ((typeof option === 'undefined' ? 'undefined' : (0, _typeof3.default)(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) { (function () { event.preventDefault(); // prevent submitting forms var option = options[activeOptionIndex]; var value = _this2._valueForSelectedOption(option); _this2.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: _this2.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 ((typeof option === 'undefined' ? 'undefined' : (0, _typeof3.default)(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 ((typeof option === 'undefined' ? 'undefined' : (0, _typeof3.default)(option)) === 'object') { return typeof option.label === 'string' ? option.label : option.value || ''; } else { return undefined === option || null === option ? '' : option; } } }, { key: '_valueEqualsOption', value: function _valueEqualsOption(value, option) { var result = false; if ((typeof value === 'undefined' ? 'undefined' : (0, _typeof3.default)(value)) === 'object') { if ((typeof option === 'undefined' ? 'undefined' : (0, _typeof3.default)(option)) === 'object') { result = value.value === option.value; } else { result = value.value === option; } } else { if ((typeof option === 'undefined' ? 'undefined' : (0, _typeof3.default)(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 _props4 = this.props, id = _props4.id, inline = _props4.inline, multiple = _props4.multiple, options = _props4.options, onSearch = _props4.onSearch, placeHolder = _props4.placeHolder, value = _props4.value; 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: placeHolder, 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 = {}, (0, _defineProperty3.default)(_classnames, CLASS_ROOT + '__option', true), (0, _defineProperty3.default)(_classnames, CLASS_ROOT + '__option--selected', selected), (0, _defineProperty3.default)(_classnames, CLASS_ROOT + '__option--active', index === activeOptionIndex), _classnames)); var content = _this4._renderLabel(option); if (option.icon) { content = _react2.default.createElement( 'span', null, option.icon, ' ', content ); } var itemOnClick = void 0; if (inline) { var itemId = id + '-' + (option.value || option); var Type = multiple ? _CheckBox2.default : _RadioButton2.default; content = _react2.default.createElement(Type, { key: itemId, id: itemId, label: content, checked: selected, onChange: _this4._onClickOption.bind(_this4, option) }); } else { itemOnClick = _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', (0, _extends3.default)({}, 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, value = _props5.value; var active = this.state.active; var intl = this.context.intl; var classes = (0, _classnames4.default)(CLASS_ROOT, (_classnames2 = {}, (0, _defineProperty3.default)(_classnames2, CLASS_ROOT + '--active', active), (0, _defineProperty3.default)(_classnames2, CLASS_ROOT + '--inline', inline), _classnames2), className); var restProps = _Props2.default.omit(this.props, (0, _keys2.default)(Select.propTypes)); if (inline) { return this._renderOptions(classes, restProps); } else { return _react2.default.createElement( 'div', { ref: function ref(_ref3) { return _this5.componentRef = _ref3; }, className: classes, onClick: this._onAddDrop }, _react2.default.createElement('input', (0, _extends3.default)({}, restProps, { ref: function ref(_ref2) { return _this5.inputRef = _ref2; }, className: INPUT + ' ' + CLASS_ROOT + '__input', disabled: true, value: this._renderValue(value) || '' })), _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 = _react.PropTypes.oneOfType([_react.PropTypes.shape({ label: _react.PropTypes.node, value: _react.PropTypes.any }), _react.PropTypes.string, _react.PropTypes.number]); Select.propTypes = { inline: _react.PropTypes.bool, multiple: _react.PropTypes.bool, onSearch: _react.PropTypes.func, onChange: _react.PropTypes.func, // (value(s)) placeHolder: _react.PropTypes.string, options: _react.PropTypes.arrayOf(valueType).isRequired, value: _react.PropTypes.oneOfType([valueType, _react.PropTypes.arrayOf(valueType)]) }; Select.contextTypes = { intl: _react.PropTypes.object }; module.exports = exports['default'];