UNPKG

material-ui

Version:

React Components that Implement Google's Material Design.

707 lines (613 loc) 21.9 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _extends2 = require('babel-runtime/helpers/extends'); var _extends3 = _interopRequireDefault(_extends2); var _typeof2 = require('babel-runtime/helpers/typeof'); var _typeof3 = _interopRequireDefault(_typeof2); var _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties'); var _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2); 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 _simpleAssign = require('simple-assign'); var _simpleAssign2 = _interopRequireDefault(_simpleAssign); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); var _keycode = require('keycode'); var _keycode2 = _interopRequireDefault(_keycode); var _TextField = require('../TextField'); var _TextField2 = _interopRequireDefault(_TextField); var _Menu = require('../Menu'); var _Menu2 = _interopRequireDefault(_Menu); var _MenuItem = require('../MenuItem'); var _MenuItem2 = _interopRequireDefault(_MenuItem); var _Divider = require('../Divider'); var _Divider2 = _interopRequireDefault(_Divider); var _Popover = require('../Popover/Popover'); var _Popover2 = _interopRequireDefault(_Popover); var _propTypes3 = require('../utils/propTypes'); var _propTypes4 = _interopRequireDefault(_propTypes3); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function getStyles(props, context, state) { var anchorEl = state.anchorEl; var fullWidth = props.fullWidth; var styles = { root: { display: 'inline-block', position: 'relative', width: fullWidth ? '100%' : 256 }, menu: { width: '100%' }, list: { display: 'block', width: fullWidth ? '100%' : 256 }, innerDiv: { overflow: 'hidden' } }; if (anchorEl && fullWidth) { styles.popover = { width: anchorEl.clientWidth }; } return styles; } var AutoComplete = function (_Component) { (0, _inherits3.default)(AutoComplete, _Component); function AutoComplete() { var _ref; var _temp, _this, _ret; (0, _classCallCheck3.default)(this, AutoComplete); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _ret = (_temp = (_this = (0, _possibleConstructorReturn3.default)(this, (_ref = AutoComplete.__proto__ || (0, _getPrototypeOf2.default)(AutoComplete)).call.apply(_ref, [this].concat(args))), _this), _this.state = { anchorEl: null, focusTextField: true, open: false, searchText: undefined }, _this.handleRequestClose = function () { // Only take into account the Popover clickAway when we are // not focusing the TextField. if (!_this.state.focusTextField) { _this.close(); } }, _this.handleMouseDown = function (event) { // Keep the TextField focused event.preventDefault(); }, _this.handleItemClick = function (event, child) { var dataSource = _this.props.dataSource; var index = parseInt(child.key, 10); var chosenRequest = dataSource[index]; var searchText = _this.chosenRequestText(chosenRequest); var updateInput = function updateInput() { return _this.props.onUpdateInput(searchText, _this.props.dataSource, { source: 'click' }); }; _this.timerClickCloseId = function () { return setTimeout(function () { _this.timerClickCloseId = null; _this.close(); _this.props.onNewRequest(chosenRequest, index); }, _this.props.menuCloseDelay); }; if (typeof _this.props.searchText !== 'undefined') { updateInput(); _this.timerClickCloseId(); } else { _this.setState({ searchText: searchText }, function () { updateInput(); _this.timerClickCloseId(); }); } }, _this.chosenRequestText = function (chosenRequest) { if (typeof chosenRequest === 'string') { return chosenRequest; } else { return chosenRequest[_this.props.dataSourceConfig.text]; } }, _this.handleEscKeyDown = function () { _this.close(); }, _this.handleKeyDown = function (event) { if (_this.props.onKeyDown) _this.props.onKeyDown(event); switch ((0, _keycode2.default)(event)) { case 'enter': _this.close(); var searchText = _this.state.searchText; if (searchText !== '') { _this.props.onNewRequest(searchText, -1); } break; case 'esc': _this.close(); break; case 'down': event.preventDefault(); _this.setState({ open: true, focusTextField: false, anchorEl: _reactDom2.default.findDOMNode(_this.refs.searchTextField) }); break; default: break; } }, _this.handleChange = function (event) { var searchText = event.target.value; // Make sure that we have a new searchText. // Fix an issue with a Cordova Webview if (searchText === _this.state.searchText) { return; } var state = { open: true, anchorEl: _reactDom2.default.findDOMNode(_this.refs.searchTextField) }; if (_this.props.searchText === undefined) { state.searchText = searchText; } _this.setState(state); _this.props.onUpdateInput(searchText, _this.props.dataSource, { source: 'change' }); }, _this.handleBlur = function (event) { if (_this.state.focusTextField && _this.timerClickCloseId === null) { _this.timerBlurClose = setTimeout(function () { _this.close(); }, 0); } if (_this.props.onBlur) { _this.props.onBlur(event); } }, _this.handleFocus = function (event) { if (!_this.state.open && _this.props.openOnFocus) { _this.setState({ open: true, anchorEl: _reactDom2.default.findDOMNode(_this.refs.searchTextField) }); } _this.setState({ focusTextField: true }); if (_this.props.onFocus) { _this.props.onFocus(event); } }, _temp), (0, _possibleConstructorReturn3.default)(_this, _ret); } (0, _createClass3.default)(AutoComplete, [{ key: 'componentWillMount', value: function componentWillMount() { this.requestsList = []; this.setState({ open: this.props.open, searchText: this.props.searchText || '' }); this.timerClickCloseId = null; } }, { key: 'componentWillReceiveProps', value: function componentWillReceiveProps(nextProps) { if (this.props.searchText !== nextProps.searchText) { this.setState({ searchText: nextProps.searchText }); } if (this.props.open !== nextProps.open) { this.setState({ open: nextProps.open, anchorEl: _reactDom2.default.findDOMNode(this.refs.searchTextField) }); } } }, { key: 'componentWillUnmount', value: function componentWillUnmount() { clearTimeout(this.timerClickCloseId); clearTimeout(this.timerBlurClose); } }, { key: 'close', value: function close() { this.setState({ open: false, anchorEl: null }); if (this.props.onClose) { this.props.onClose(); } } }, { key: 'blur', value: function blur() { this.refs.searchTextField.blur(); } }, { key: 'focus', value: function focus() { this.refs.searchTextField.focus(); } }, { key: 'render', value: function render() { var _this2 = this; var _props = this.props, anchorOrigin = _props.anchorOrigin, animated = _props.animated, animation = _props.animation, dataSource = _props.dataSource, dataSourceConfig = _props.dataSourceConfig, disableFocusRipple = _props.disableFocusRipple, errorStyle = _props.errorStyle, floatingLabelText = _props.floatingLabelText, filter = _props.filter, fullWidth = _props.fullWidth, style = _props.style, hintText = _props.hintText, maxSearchResults = _props.maxSearchResults, menuCloseDelay = _props.menuCloseDelay, textFieldStyle = _props.textFieldStyle, menuStyle = _props.menuStyle, menuProps = _props.menuProps, listStyle = _props.listStyle, targetOrigin = _props.targetOrigin, onBlur = _props.onBlur, onClose = _props.onClose, onFocus = _props.onFocus, onKeyDown = _props.onKeyDown, onNewRequest = _props.onNewRequest, onUpdateInput = _props.onUpdateInput, openOnFocus = _props.openOnFocus, popoverProps = _props.popoverProps, searchTextProp = _props.searchText, other = (0, _objectWithoutProperties3.default)(_props, ['anchorOrigin', 'animated', 'animation', 'dataSource', 'dataSourceConfig', 'disableFocusRipple', 'errorStyle', 'floatingLabelText', 'filter', 'fullWidth', 'style', 'hintText', 'maxSearchResults', 'menuCloseDelay', 'textFieldStyle', 'menuStyle', 'menuProps', 'listStyle', 'targetOrigin', 'onBlur', 'onClose', 'onFocus', 'onKeyDown', 'onNewRequest', 'onUpdateInput', 'openOnFocus', 'popoverProps', 'searchText']); var _ref2 = popoverProps || {}, popoverStyle = _ref2.style, popoverOther = (0, _objectWithoutProperties3.default)(_ref2, ['style']); var _state = this.state, open = _state.open, anchorEl = _state.anchorEl, searchText = _state.searchText, focusTextField = _state.focusTextField; var prepareStyles = this.context.muiTheme.prepareStyles; var styles = getStyles(this.props, this.context, this.state); var requestsList = []; dataSource.every(function (item, index) { switch (typeof item === 'undefined' ? 'undefined' : (0, _typeof3.default)(item)) { case 'string': if (filter(searchText, item, item)) { requestsList.push({ text: item, value: _react2.default.createElement(_MenuItem2.default, { innerDivStyle: styles.innerDiv, value: item, primaryText: item, disableFocusRipple: disableFocusRipple, key: index }) }); } break; case 'object': if (item && typeof item[_this2.props.dataSourceConfig.text] === 'string') { var itemText = item[_this2.props.dataSourceConfig.text]; if (!_this2.props.filter(searchText, itemText, item)) break; var itemValue = item[_this2.props.dataSourceConfig.value]; if (itemValue && itemValue.type && (itemValue.type.muiName === _MenuItem2.default.muiName || itemValue.type.muiName === _Divider2.default.muiName)) { requestsList.push({ text: itemText, value: _react2.default.cloneElement(itemValue, { key: index, disableFocusRipple: disableFocusRipple }) }); } else { requestsList.push({ text: itemText, value: _react2.default.createElement(_MenuItem2.default, { innerDivStyle: styles.innerDiv, primaryText: itemText, disableFocusRipple: disableFocusRipple, key: index }) }); } } break; default: // Do nothing } return !(maxSearchResults && maxSearchResults > 0 && requestsList.length === maxSearchResults); }); this.requestsList = requestsList; var menu = open && requestsList.length > 0 && _react2.default.createElement( _Menu2.default, (0, _extends3.default)({ ref: 'menu', autoWidth: false, disableAutoFocus: focusTextField, onEscKeyDown: this.handleEscKeyDown, initiallyKeyboardFocused: true, onItemClick: this.handleItemClick, onMouseDown: this.handleMouseDown, style: (0, _simpleAssign2.default)(styles.menu, menuStyle), listStyle: (0, _simpleAssign2.default)(styles.list, listStyle) }, menuProps), requestsList.map(function (i) { return i.value; }) ); return _react2.default.createElement( 'div', { style: prepareStyles((0, _simpleAssign2.default)(styles.root, style)) }, _react2.default.createElement(_TextField2.default, (0, _extends3.default)({ ref: 'searchTextField', autoComplete: 'off', onBlur: this.handleBlur, onFocus: this.handleFocus, onKeyDown: this.handleKeyDown, floatingLabelText: floatingLabelText, hintText: hintText, fullWidth: fullWidth, multiLine: false, errorStyle: errorStyle, style: textFieldStyle }, other, { // value and onChange are idiomatic properties often leaked. // We prevent their overrides in order to reduce potential bugs. value: searchText, onChange: this.handleChange })), _react2.default.createElement( _Popover2.default, (0, _extends3.default)({ style: (0, _simpleAssign2.default)({}, styles.popover, popoverStyle), canAutoPosition: false, anchorOrigin: anchorOrigin, targetOrigin: targetOrigin, open: open, anchorEl: anchorEl, useLayerForClickAway: false, onRequestClose: this.handleRequestClose, animated: animated, animation: animation }, popoverOther), menu ) ); } }]); return AutoComplete; }(_react.Component); AutoComplete.defaultProps = { anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, animated: true, dataSourceConfig: { text: 'text', value: 'value' }, disableFocusRipple: true, filter: function filter(searchText, key) { return searchText !== '' && key.indexOf(searchText) !== -1; }, fullWidth: false, open: false, openOnFocus: false, onUpdateInput: function onUpdateInput() {}, onNewRequest: function onNewRequest() {}, menuCloseDelay: 300, targetOrigin: { vertical: 'top', horizontal: 'left' } }; AutoComplete.contextTypes = { muiTheme: _propTypes2.default.object.isRequired }; AutoComplete.propTypes = process.env.NODE_ENV !== "production" ? { /** * Location of the anchor for the auto complete. */ anchorOrigin: _propTypes4.default.origin, /** * If true, the auto complete is animated as it is toggled. */ animated: _propTypes2.default.bool, /** * Override the default animation component used. */ animation: _propTypes2.default.func, /** * Array of strings or nodes used to populate the list. */ dataSource: _propTypes2.default.array.isRequired, /** * Config for objects list dataSource. * * @typedef {Object} dataSourceConfig * * @property {string} text `dataSource` element key used to find a string to be matched for search * and shown as a `TextField` input value after choosing the result. * @property {string} value `dataSource` element key used to find a string to be shown in search results. */ dataSourceConfig: _propTypes2.default.object, /** * Disables focus ripple when true. */ disableFocusRipple: _propTypes2.default.bool, /** * Override style prop for error. */ errorStyle: _propTypes2.default.object, /** * The error content to display. */ errorText: _propTypes2.default.node, /** * Callback function used to filter the auto complete. * * @param {string} searchText The text to search for within `dataSource`. * @param {string} key `dataSource` element, or `text` property on that element if it's not a string. * @returns {boolean} `true` indicates the auto complete list will include `key` when the input is `searchText`. */ filter: _propTypes2.default.func, /** * The content to use for adding floating label element. */ floatingLabelText: _propTypes2.default.node, /** * If true, the field receives the property `width: 100%`. */ fullWidth: _propTypes2.default.bool, /** * The hint content to display. */ hintText: _propTypes2.default.node, /** * Override style for list. */ listStyle: _propTypes2.default.object, /** * The max number of search results to be shown. * By default it shows all the items which matches filter. */ maxSearchResults: _propTypes2.default.number, /** * Delay for closing time of the menu. */ menuCloseDelay: _propTypes2.default.number, /** * Props to be passed to menu. */ menuProps: _propTypes2.default.object, /** * Override style for menu. */ menuStyle: _propTypes2.default.object, /** @ignore */ onBlur: _propTypes2.default.func, /** * Callback function fired when the menu is closed. */ onClose: _propTypes2.default.func, /** @ignore */ onFocus: _propTypes2.default.func, /** @ignore */ onKeyDown: _propTypes2.default.func, /** * Callback function that is fired when a list item is selected, or enter is pressed in the `TextField`. * * @param {string} chosenRequest Either the `TextField` input value, if enter is pressed in the `TextField`, * or the dataSource object corresponding to the list item that was selected. * @param {number} index The index in `dataSource` of the list item selected, or `-1` if enter is pressed in the * `TextField`. */ onNewRequest: _propTypes2.default.func, /** * Callback function that is fired when the user updates the `TextField`. * * @param {string} searchText The auto-complete's `searchText` value. * @param {array} dataSource The auto-complete's `dataSource` array. * @param {object} params Additional information linked the update. */ onUpdateInput: _propTypes2.default.func, /** * Auto complete menu is open if true. */ open: _propTypes2.default.bool, /** * If true, the list item is showed when a focus event triggers. */ openOnFocus: _propTypes2.default.bool, /** * Props to be passed to popover. */ popoverProps: _propTypes2.default.object, /** * Text being input to auto complete. */ searchText: _propTypes2.default.string, /** * Override the inline-styles of the root element. */ style: _propTypes2.default.object, /** * Origin for location of target. */ targetOrigin: _propTypes4.default.origin, /** * Override the inline-styles of AutoComplete's TextField element. */ textFieldStyle: _propTypes2.default.object } : {}; AutoComplete.levenshteinDistance = function (searchText, key) { var current = []; var prev = void 0; var value = void 0; for (var i = 0; i <= key.length; i++) { for (var j = 0; j <= searchText.length; j++) { if (i && j) { if (searchText.charAt(j - 1) === key.charAt(i - 1)) value = prev;else value = Math.min(current[j], current[j - 1], prev) + 1; } else { value = i + j; } prev = current[j]; current[j] = value; } } return current.pop(); }; AutoComplete.noFilter = function () { return true; }; AutoComplete.defaultFilter = AutoComplete.caseSensitiveFilter = function (searchText, key) { return searchText !== '' && key.indexOf(searchText) !== -1; }; AutoComplete.caseInsensitiveFilter = function (searchText, key) { return key.toLowerCase().indexOf(searchText.toLowerCase()) !== -1; }; AutoComplete.levenshteinDistanceFilter = function (distanceLessThan) { if (distanceLessThan === undefined) { return AutoComplete.levenshteinDistance; } else if (typeof distanceLessThan !== 'number') { throw 'Error: AutoComplete.levenshteinDistanceFilter is a filter generator, not a filter!'; } return function (s, k) { return AutoComplete.levenshteinDistance(s, k) < distanceLessThan; }; }; AutoComplete.fuzzyFilter = function (searchText, key) { var compareString = key.toLowerCase(); searchText = searchText.toLowerCase(); var searchTextIndex = 0; for (var index = 0; index < key.length; index++) { if (compareString[index] === searchText[searchTextIndex]) { searchTextIndex += 1; } } return searchTextIndex === searchText.length; }; AutoComplete.Item = _MenuItem2.default; AutoComplete.Divider = _Divider2.default; exports.default = AutoComplete;