d2-ui
Version:
631 lines (545 loc) • 20.6 kB
JavaScript
'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 ? "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 _simpleAssign = require('simple-assign');
var _simpleAssign2 = _interopRequireDefault(_simpleAssign);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
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 _propTypes = require('../utils/propTypes');
var _propTypes2 = _interopRequireDefault(_propTypes);
var _warning = require('warning');
var _warning2 = _interopRequireDefault(_warning);
var _deprecatedPropType = require('../utils/deprecatedPropType');
var _deprecatedPropType2 = _interopRequireDefault(_deprecatedPropType);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
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; }
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) {
_inherits(AutoComplete, _Component);
function AutoComplete() {
var _Object$getPrototypeO;
var _temp, _this, _ret;
_classCallCheck(this, AutoComplete);
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_Object$getPrototypeO = Object.getPrototypeOf(AutoComplete)).call.apply(_Object$getPrototypeO, [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.handleItemTouchTap = function (event, child) {
var dataSource = _this.props.dataSource;
var index = parseInt(child.key, 10);
var chosenRequest = dataSource[index];
var searchText = typeof chosenRequest === 'string' ? chosenRequest : chosenRequest.text;
_this.props.onNewRequest(chosenRequest, index);
_this.timerTouchTapCloseId = setTimeout(function () {
_this.setState({
searchText: searchText
});
_this.close();
_this.timerTouchTapCloseId = null;
}, _this.props.menuCloseDelay);
}, _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;
}
_this.setState({
searchText: searchText,
open: true,
anchorEl: _reactDom2.default.findDOMNode(_this.refs.searchTextField)
}, function () {
_this.props.onUpdateInput(searchText, _this.props.dataSource);
});
}, _this.handleBlur = function (event) {
if (_this.state.focusTextField && _this.timerTouchTapCloseId === null) {
_this.close();
}
if (_this.props.onBlur) {
_this.props.onBlur(event);
}
}, _this.handleFocus = function (event) {
if (!_this.state.open && (_this.props.triggerUpdateOnFocus || _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), _possibleConstructorReturn(_this, _ret);
}
_createClass(AutoComplete, [{
key: 'componentWillMount',
value: function componentWillMount() {
this.requestsList = [];
this.setState({
open: this.props.open,
searchText: this.props.searchText
});
this.timerTouchTapCloseId = null;
}
}, {
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(nextProps) {
if (this.props.searchText !== nextProps.searchText) {
this.setState({
searchText: nextProps.searchText
});
}
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
clearTimeout(this.timerTouchTapCloseId);
}
}, {
key: 'close',
value: function close() {
this.setState({
open: false,
anchorEl: null
});
}
}, {
key: 'setValue',
value: function setValue(textValue) {
process.env.NODE_ENV !== "production" ? (0, _warning2.default)(false, 'setValue() is deprecated, use the searchText property.') : void 0;
this.setState({
searchText: textValue
});
}
}, {
key: 'getValue',
value: function getValue() {
process.env.NODE_ENV !== "production" ? (0, _warning2.default)(false, 'getValue() is deprecated.') : void 0;
return this.state.searchText;
}
}, {
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;
var anchorOrigin = _props.anchorOrigin;
var animated = _props.animated;
var style = _props.style;
var errorStyle = _props.errorStyle;
var floatingLabelText = _props.floatingLabelText;
var hintText = _props.hintText;
var fullWidth = _props.fullWidth;
var menuStyle = _props.menuStyle;
var menuProps = _props.menuProps;
var listStyle = _props.listStyle;
var targetOrigin = _props.targetOrigin;
var disableFocusRipple = _props.disableFocusRipple;
var triggerUpdateOnFocus = _props.triggerUpdateOnFocus;
var // eslint-disable-line no-unused-vars
openOnFocus = _props.openOnFocus;
var // eslint-disable-line no-unused-vars
maxSearchResults = _props.maxSearchResults;
var dataSource = _props.dataSource;
var other = _objectWithoutProperties(_props, ['anchorOrigin', 'animated', 'style', 'errorStyle', 'floatingLabelText', 'hintText', 'fullWidth', 'menuStyle', 'menuProps', 'listStyle', 'targetOrigin', 'disableFocusRipple', 'triggerUpdateOnFocus', 'openOnFocus', 'maxSearchResults', 'dataSource']);
var _state = this.state;
var open = _state.open;
var anchorEl = _state.anchorEl;
var searchText = _state.searchText;
var 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' : _typeof(item)) {
case 'string':
if (_this2.props.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.text === 'string') {
if (_this2.props.filter(searchText, item.text, item)) {
if (item.value.type && (item.value.type.muiName === _MenuItem2.default.muiName || item.value.type.muiName === _Divider2.default.muiName)) {
requestsList.push({
text: item.text,
value: _react2.default.cloneElement(item.value, {
key: index,
disableFocusRipple: _this2.props.disableFocusRipple
})
});
} else {
requestsList.push({
text: item.text,
value: _react2.default.createElement(_MenuItem2.default, {
innerDivStyle: styles.innerDiv,
primaryText: item.value,
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,
_extends({}, menuProps, {
ref: 'menu',
autoWidth: false,
disableAutoFocus: focusTextField,
onEscKeyDown: this.handleEscKeyDown,
initiallyKeyboardFocused: false,
onItemTouchTap: this.handleItemTouchTap,
onMouseDown: this.handleMouseDown,
style: (0, _simpleAssign2.default)(styles.menu, menuStyle),
listStyle: (0, _simpleAssign2.default)(styles.list, listStyle)
}),
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, _extends({}, other, {
ref: 'searchTextField',
autoComplete: 'off',
value: searchText,
onChange: this.handleChange,
onBlur: this.handleBlur,
onFocus: this.handleFocus,
onKeyDown: this.handleKeyDown,
floatingLabelText: floatingLabelText,
hintText: hintText,
fullWidth: fullWidth,
multiLine: false,
errorStyle: errorStyle
})),
_react2.default.createElement(
_Popover2.default,
{
style: styles.popover,
canAutoPosition: false,
anchorOrigin: anchorOrigin,
targetOrigin: targetOrigin,
open: open,
anchorEl: anchorEl,
useLayerForClickAway: false,
onRequestClose: this.handleRequestClose,
animated: animated
},
menu
)
);
}
}]);
return AutoComplete;
}(_react.Component);
AutoComplete.propTypes = {
/**
* Location of the anchor for the auto complete.
*/
anchorOrigin: _propTypes2.default.origin,
/**
* If true, the auto complete is animated as it is toggled.
*/
animated: _react.PropTypes.bool,
/**
* Array of strings or nodes used to populate the list.
*/
dataSource: _react.PropTypes.array.isRequired,
/**
* Disables focus ripple when true.
*/
disableFocusRipple: _react.PropTypes.bool,
/**
* Override style prop for error.
*/
errorStyle: _react.PropTypes.object,
/**
* The error content to display.
*/
errorText: _react.PropTypes.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: _react.PropTypes.func,
/**
* The content to use for adding floating label element.
*/
floatingLabelText: _react.PropTypes.node,
/**
* If true, the field receives the property `width: 100%`.
*/
fullWidth: _react.PropTypes.bool,
/**
* The hint content to display.
*/
hintText: _react.PropTypes.node,
/**
* Override style for list.
*/
listStyle: _react.PropTypes.object,
/**
* The max number of search results to be shown.
* By default it shows all the items which matches filter.
*/
maxSearchResults: _react.PropTypes.number,
/**
* Delay for closing time of the menu.
*/
menuCloseDelay: _react.PropTypes.number,
/**
* Props to be passed to menu.
*/
menuProps: _react.PropTypes.object,
/**
* Override style for menu.
*/
menuStyle: _react.PropTypes.object,
/**
* Callback function that is fired when the `TextField` loses focus.
*
* @param {object} event `blur` event targeting the `TextField`.
*/
onBlur: _react.PropTypes.func,
/**
* Callback function that is fired when the `TextField` gains focus.
*
* @param {object} event `focus` event targeting the `TextField`.
*/
onFocus: _react.PropTypes.func,
/**
* Callback function that is fired when the `TextField` receives a keydown event.
*/
onKeyDown: _react.PropTypes.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 text value of the corresponding 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: _react.PropTypes.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.
*/
onUpdateInput: _react.PropTypes.func,
/**
* Auto complete menu is open if true.
*/
open: _react.PropTypes.bool,
/**
* If true, the list item is showed when a focus event triggers.
*/
openOnFocus: _react.PropTypes.bool,
/**
* Text being input to auto complete.
*/
searchText: _react.PropTypes.string,
/**
* Override the inline-styles of the root element.
*/
style: _react.PropTypes.object,
/**
* Origin for location of target.
*/
targetOrigin: _propTypes2.default.origin,
/**
* If true, will update when focus event triggers.
*/
triggerUpdateOnFocus: (0, _deprecatedPropType2.default)(_react.PropTypes.bool, 'Instead, use openOnFocus')
};
AutoComplete.defaultProps = {
anchorOrigin: {
vertical: 'bottom',
horizontal: 'left'
},
animated: true,
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() {},
searchText: '',
menuCloseDelay: 300,
targetOrigin: {
vertical: 'top',
horizontal: 'left'
}
};
AutoComplete.contextTypes = {
muiTheme: _react.PropTypes.object.isRequired
};
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) {
if (searchText.length === 0) {
return false;
}
var subMatchKey = key.substring(0, searchText.length);
var distance = AutoComplete.levenshteinDistance(searchText.toLowerCase(), subMatchKey.toLowerCase());
return searchText.length > 3 ? distance < 2 : distance === 0;
};
AutoComplete.Item = _MenuItem2.default;
AutoComplete.Divider = _Divider2.default;
exports.default = AutoComplete;