UNPKG

react-sm-select

Version:
640 lines (503 loc) 20.2 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.MultiSelect = undefined; 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 _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 _utils = require('./utils'); var u = _interopRequireWildcard(_utils); var _consts = require('./consts'); var _Header = require('./Header'); var _dropdown = require('./dropdown'); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } 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; } var MultiSelect = exports.MultiSelect = function (_React$Component) { _inherits(MultiSelect, _React$Component); function MultiSelect(p) { _classCallCheck(this, MultiSelect); var _this = _possibleConstructorReturn(this, (MultiSelect.__proto__ || Object.getPrototypeOf(MultiSelect)).call(this, p)); _initialiseProps.call(_this); _this.state = { value: p.isLoading ? p.value : u.omitDirtyValues(p.options, p.value, _this.isSingle()), expanded: false, hasFocus: false, selectAll: false, focusIndex: p.enableSearch ? -1 : -2, searchText: '', mouseHover: false }; _this.multiSelectRef = _react2.default.createRef(); _this.headerRef = _react2.default.createRef(); _this.searchRef = _react2.default.createRef(); _this.hasListener = false; return _this; } _createClass(MultiSelect, [{ key: 'componentDidUpdate', value: function componentDidUpdate(pp, ps) { var _this2 = this; var p = this.props, s = this.state; var loadingStart = !pp.isLoading && p.isLoading; var loadingEnd = pp.isLoading && !p.isLoading; var optionsChanges = pp.options.length !== p.options.length; var getClearValue = function getClearValue(value) { return loadingStart ? value : u.omitDirtyValues(p.options, value, _this2.isSingle()); }; var clearCurrValue = getClearValue(p.value); var clearPrevValue = getClearValue(pp.value); if (!u.areArraysEqual(clearPrevValue, clearCurrValue) || loadingStart || loadingEnd || optionsChanges) this.setState({ value: clearCurrValue }); // Call onClose if it was closed if (ps.expanded === true && s.expanded === false) this.onEvent('onClose'); // Call onChange if value was changed if (!u.areArraysEqual(ps.value, s.value)) this.onEvent('onChange'); // Subscribe - Unsubscribe for click outside if enabled - disabled if (pp.disabled && !p.disabled) { this.hasListener = true; u.attachDocumentClickListener(this.handleDocumentClick); } if (!pp.disabled && p.disabled) { this.hasListener = false; u.removeDocumentClickListener(this.handleDocumentClick); } } }, { key: 'componentWillUnmount', value: function componentWillUnmount() { if (this.hasListener) u.removeDocumentClickListener(this.handleDocumentClick); } // Common /** * Checks if mode is single * @returns {boolean} */ /** * Handle click on document, to detect click outside */ /** * Handle MultiSelect click to subscribe for click outside */ /** * Handle focus to control focus state */ /** * Handle blur to control focus state */ /** * Toggle MultiSelect DropDown * @param event * @param value Boolean */ /** * Handle hover to trigger DropDown list * @param expanded Boolean */ /** * Detect if SelectAll should be visible * @returns {boolean|Boolean} */ /** * Handle focus over search field and header depend on focus index * @param focusIndex Number */ // Keyboard Navigation /** * Handle Keyboard Key Down and set focus * Skip search and select all if needed * @param event */ /** * Handle Keyboard Key Up and set focus * Skip search and select all if needed * @param event */ /** * Resets value if it needed * @param event */ /** * Handle Key Press * @param event */ // Value /** * Add selected value to selected or select in single mode * @param optionValue value string */ /** * Removes/Deselect value at index * @param index Number value index * @param event */ /** * Resets value to provided state or default * @param event */ // Search /** * Handle search field changes * @param event */ // Select All /** * Checks if all options are selected * @returns {boolean} */ /** * Select/Deselect all options */ // Options /** * Returns array of filtered options used by search field * @returns [] */ /** * Handle Option Click * @param optionValue String * @param index Number */ // Events /** * Any event coming from props * @param event */ }, { key: 'render', value: function render() { var _this3 = this; var p = this.props, s = this.state; return _react2.default.createElement( 'div', { className: 'MultiSelect', id: p.id, ref: this.multiSelectRef, onMouseDown: this.handleClick, onMouseEnter: function onMouseEnter() { return _this3.handleHover(true); }, onMouseLeave: function onMouseLeave() { return _this3.handleHover(false); }, onKeyDown: this.handleKeyPress, onFocus: this.handleFocus, onBlur: this.handleBlur }, _react2.default.createElement( _Header.Header, { nodeRef: this.headerRef, focused: s.hasFocus, expanded: s.expanded, disabled: p.disabled, selected: s.focusIndex === -2, onClick: this.toggleDropDown }, _react2.default.createElement( 'div', { className: u.classes('Header__value', { 'Header__value--resetable': p.resetable && (!!s.value.length || !!p.resetTo.length) }) }, _react2.default.createElement(_Header.Value, { mode: p.mode, options: p.options, value: s.value, Value: p.Value, valuePlaceholder: p.valuePlaceholder, allSelectedLabel: p.allSelectedLabel, counterLabel: p.counterLabel, Tag: p.Tag, removableTag: p.removableTag, onRemove: this.deselect }) ), _react2.default.createElement( 'div', { className: 'Header__controls' }, p.resetable && (!!s.value.length || !!p.resetTo.length) && _react2.default.createElement( 'div', { className: 'Header__reset', onClick: this.reset }, '\u2715' ), p.isLoading && _react2.default.createElement(p.Loading, null), !p.isLoading && _react2.default.createElement(p.Arrow, { value: s.value, options: p.options, hasFocus: s.hasFocus, disabled: p.disabled, expanded: s.expanded }) ) ), s.expanded && _react2.default.createElement( 'div', { className: 'DropDown', role: 'listbox' }, p.enableSearch && _react2.default.createElement('input', { type: 'text', className: u.classes('DropDown__searchField', { 'DropDown__searchField--selected': s.focusIndex === -1 }), placeholder: !p.maxOptionsToRender ? p.searchPlaceholder : p.searchMorePlaceholder, value: s.searchText, ref: this.searchRef, onChange: this.handleSearchChange, onMouseDown: function onMouseDown() { return _this3.setState({ focusIndex: -1 }); }, autoFocus: s.focusIndex === -1 }), _react2.default.createElement( 'ul', { className: 'OptionList' }, this.isSelectAllVisible() && _react2.default.createElement(_dropdown.SelectAll, { key: s.value || this.filteredOptions(), Option: p.Option, focused: s.focusIndex === 0, checked: this.allAreSelected(), selectAllLabel: p.selectAllLabel, onClick: this.toggleAll }), this.filteredOptions().map(function (option, index) { return _react2.default.createElement( 'li', { className: 'OptionList__item', key: option.value }, _react2.default.createElement(_dropdown.Option, { key: s.value || _this3.filteredOptions(), isSingle: _this3.isSingle(), focused: s.focusIndex === index + 1, checked: s.value.includes(option.value), option: option, Option: p.Option, onClick: function onClick() { return _this3.optionClick(option.value, index); } }) ); }) ) ) ); } }]); return MultiSelect; }(_react2.default.Component); MultiSelect.displayName = 'MultiSelect'; MultiSelect.propTypes = { // data id: _propTypes2.default.string, mode: _propTypes2.default.oneOf([_consts.MODE.LIST, _consts.MODE.TAGS, _consts.MODE.COUNTER, _consts.MODE.SINGLE]), options: _propTypes2.default.arrayOf(_propTypes2.default.shape({ value: _propTypes2.default.string, label: _propTypes2.default.string })).isRequired, value: _propTypes2.default.arrayOf(_propTypes2.default.string), resetTo: _propTypes2.default.arrayOf(_propTypes2.default.string), maxOptionsToRender: _propTypes2.default.number, // methods onChange: _propTypes2.default.func, onBlur: _propTypes2.default.func, onClose: _propTypes2.default.func, // custom rendering Value: _propTypes2.default.func, Tag: _propTypes2.default.func, Loading: _propTypes2.default.func, Arrow: _propTypes2.default.func, Option: _propTypes2.default.func, // search filterOptions: _propTypes2.default.func, // labels / placeholders valuePlaceholder: _propTypes2.default.string, allSelectedLabel: _propTypes2.default.string, counterLabel: _propTypes2.default.string, searchPlaceholder: _propTypes2.default.string, searchMorePlaceholder: _propTypes2.default.string, selectAllLabel: _propTypes2.default.string, // controls disabled: _propTypes2.default.bool, shouldToggleOnHover: _propTypes2.default.bool, isLoading: _propTypes2.default.bool, removableTag: _propTypes2.default.bool, resetable: _propTypes2.default.bool, enableSearch: _propTypes2.default.bool, hasSelectAll: _propTypes2.default.bool, stopClickPropagation: _propTypes2.default.bool }; MultiSelect.defaultProps = { mode: _consts.MODE.LIST, value: [], resetTo: [], Loading: _Header.DefLoading, Arrow: _Header.DefArrow, filterOptions: u.defaultFilterOptions, valuePlaceholder: 'Select', allSelectedLabel: 'All items are selected', searchPlaceholder: 'Search', searchMorePlaceholder: 'Search to see more ...', selectAllLabel: 'Select All', shouldToggleOnHover: false, removableTag: true, resetable: false, enableSearch: false, hasSelectAll: false, stopClickPropagation: false }; var _initialiseProps = function _initialiseProps() { var _this4 = this; this.isSingle = function () { return _this4.props.mode === _consts.MODE.SINGLE; }; this.handleDocumentClick = function () { if (_this4.props.disabled) return; if (!_this4.state.mouseHover) { _this4.setState({ expanded: false, hasFocus: false }); u.removeDocumentClickListener(_this4.handleDocumentClick); _this4.onEvent('onBlur'); } }; this.handleClick = function () { if (!_this4.props.disabled && !_this4.hasListener) u.attachDocumentClickListener(_this4.handleDocumentClick); }; this.handleFocus = function () { _this4.setState(function (_ref) { var hasFocus = _ref.hasFocus; return !hasFocus ? { hasFocus: true } : null; }); }; this.handleBlur = function () { _this4.setState(function (_ref2) { var hasFocus = _ref2.hasFocus; return hasFocus ? { hasFocus: false } : null; }); }; this.toggleDropDown = function (event, value) { var p = _this4.props; if (p.disabled || p.isLoading) return; _this4.setState(function (_ref3) { var expanded = _ref3.expanded; return _extends({ expanded: value !== undefined ? value : !expanded }, !expanded ? { focusIndex: p.enableSearch ? -1 : -2, searchText: '' } : {}); }); if (event && p.stopClickPropagation) u.stopPreventPropagation(event); }; this.handleHover = function (expanded) { _this4.setState({ mouseHover: expanded }); if (_this4.props.shouldToggleOnHover) _this4.toggleDropDown(null, expanded); }; this.isSelectAllVisible = function () { return !_this4.isSingle() && _this4.props.hasSelectAll && !_this4.state.searchText; }; this.handleFocusControl = function (focusIndex) { if (focusIndex === -1) _this4.searchRef.current.focus(); if (focusIndex === -2) _this4.headerRef.current.focus(); }; this.keyDown = function (event) { var p = _this4.props, s = _this4.state; if (!s.expanded) _this4.toggleDropDown(null, true);else _this4.setState(function (_ref4) { var focusIndex = _ref4.focusIndex; var nextIndex = focusIndex + 1; if (nextIndex === -1) nextIndex = p.enableSearch ? nextIndex : nextIndex + 1; if (nextIndex === 0) nextIndex = _this4.isSelectAllVisible() ? nextIndex : nextIndex + 1; return s.focusIndex < _this4.filteredOptions().length ? { focusIndex: nextIndex } : null; }, function () { _this4.handleFocusControl(_this4.state.focusIndex); }); u.stopPreventPropagation(event); }; this.keyUp = function (event) { var p = _this4.props, s = _this4.state; if (s.expanded) { if (s.focusIndex === -2) _this4.toggleDropDown(null, false);else _this4.setState(function (_ref5) { var focusIndex = _ref5.focusIndex; var nextIndex = focusIndex - 1; if (nextIndex === 0) nextIndex = _this4.isSelectAllVisible() ? nextIndex : nextIndex - 1; if (nextIndex === -1) nextIndex = p.enableSearch ? nextIndex : nextIndex - 1; return { focusIndex: nextIndex }; }, function () { _this4.handleFocusControl(_this4.state.focusIndex); }); } u.stopPreventPropagation(event); }; this.clearValue = function (event) { if (_this4.props.resetable && _this4.state.focusIndex === -2) _this4.reset(event); }; this.handleKeyPress = function (event) { var _event$which$8$9$27$; (_event$which$8$9$27$ = {}, _defineProperty(_event$which$8$9$27$, event.which, function () {}), _defineProperty(_event$which$8$9$27$, 8, function _() { return _this4.clearValue(event); }), _defineProperty(_event$which$8$9$27$, 9, function _() { return _this4.toggleDropDown(null, false); }), _defineProperty(_event$which$8$9$27$, 27, function _() { // Esc _this4.toggleDropDown(null, false); _this4.handleFocusControl(-2); u.stopPreventPropagation(event); }), _defineProperty(_event$which$8$9$27$, 38, function _() { return _this4.keyUp(event); }), _defineProperty(_event$which$8$9$27$, 40, function _() { return _this4.keyDown(event); }), _event$which$8$9$27$)[event.which](); }; this.select = function (optionValue) { if (_this4.isSingle()) _this4.setState({ value: [optionValue] }, function () { return _this4.toggleDropDown(null, false); });else _this4.setState({ value: [].concat(_toConsumableArray(_this4.state.value), [optionValue]) }); }; this.deselect = function (index, event) { var value = _this4.state.value; _this4.setState({ value: [].concat(_toConsumableArray(value.slice(0, index)), _toConsumableArray(value.slice(index + 1))) }); if (event) u.stopPreventPropagation(event); }; this.reset = function (event) { var p = _this4.props; if (!p.disabled || !p.isLoading) _this4.setState({ value: p.resetTo }); u.stopPreventPropagation(event); }; this.handleSearchChange = function (event) { _this4.setState({ searchText: event.target.value }); }; this.allAreSelected = function () { return _this4.props.options.length === _this4.state.value.length; }; this.toggleAll = function () { if (!_this4.allAreSelected()) { var value = _this4.props.options.map(function (option) { return option.value; }); _this4.setState({ value: value, focusIndex: 0 }); } else _this4.setState({ value: [], focusIndex: 0 }); }; this.filteredOptions = function () { var s = _this4.state, p = _this4.props; var optionsToRender = p.filterOptions(p.options, s.searchText); return p.maxOptionsToRender ? optionsToRender.slice(0, p.maxOptionsToRender) : optionsToRender; }; this.optionClick = function (optionValue, index) { var s = _this4.state; var valueIndex = s.value.indexOf(optionValue); if (valueIndex === -1 || _this4.isSingle()) _this4.select(optionValue);else _this4.deselect(valueIndex); _this4.setState({ focusIndex: index + 1 }); }; this.onEvent = function (event) { var p = _this4.props, s = _this4.state; if (p[event]) p[event](s.value); }; };