UNPKG

react-query-assist

Version:
477 lines (389 loc) 15.5 kB
'use strict'; exports.__esModule = true; exports.default = 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 _class, _temp; var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); var _reactPageClick = require('react-page-click'); var _reactPageClick2 = _interopRequireDefault(_reactPageClick); var _token = require('./utils/token'); var _dropdown = require('./components/dropdown'); var _dropdown2 = _interopRequireDefault(_dropdown); var _index = require('./index.styl'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: 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 _default = (_temp = _class = function (_Component) { _inherits(_default, _Component); function _default(props) { _classCallCheck(this, _default); var _this = _possibleConstructorReturn(this, _Component.call(this, props)); _this.onFocus = _this.onFocus.bind(_this); _this.onBlur = _this.onBlur.bind(_this); _this.onKeyDown = _this.onKeyDown.bind(_this); _this.onChange = _this.onChange.bind(_this); _this.onAutosuggest = _this.onAutosuggest.bind(_this); _this.onSelectValue = _this.onSelectValue.bind(_this); _this.handleEnterKey = _this.handleEnterKey.bind(_this); _this.shouldAutosuggest = _this.shouldAutosuggest.bind(_this); _this.onClose = _this.onClose.bind(_this); // this.onClickToken = this.onClickToken.bind(this) _this.extract = _this.extract.bind(_this); _this.getCurrentChunk = _this.getCurrentChunk.bind(_this); _this.buildOverlay = _this.buildOverlay.bind(_this); _this.state = { focused: false, value: props.defaultValue, attributes: props.data, overlayComponents: [], dropdownClosed: false, dropdownOpen: false, dropdownValue: null, dropdownX: null, dropdownY: null }; return _this; } _default.prototype.componentDidMount = function componentDidMount() { this.setState({ overlayComponents: this.buildOverlay(this.state.value) }); }; _default.prototype.componentDidUpdate = function componentDidUpdate(prevProps, prevState) { var _state = this.state, value = _state.value, attributes = _state.attributes; if (value !== prevState.value) { this.props.onChange(value); } if (value !== prevState.value || attributes.length !== prevState.attributes.length) { this.setState({ overlayComponents: this.buildOverlay(value) }, this.onAutosuggest); } }; _default.prototype.componentWillReceiveProps = function componentWillReceiveProps(nextProps) { var newState = {}; // default value can be empty string (to clear search) if (nextProps.defaultValue !== undefined) { newState.value = nextProps.defaultValue; } if (nextProps.data) { newState.attributes = nextProps.data; } this.setState(newState); }; _default.prototype.onFocus = function onFocus(evt) { this.setState({ focused: true }, this.onAutosuggest); }; _default.prototype.onBlur = function onBlur(evt) { this.setState({ focused: false }); }; _default.prototype.onKeyDown = function onKeyDown(evt) { if (evt.keyCode === 13) { this.handleEnterKey(evt); } // close dropdown if navigating with arrow keys if (evt.keyCode === 37 || evt.keyCode === 39) { this.onClose(); } }; _default.prototype.onChange = function onChange(evt) { this.setState({ value: evt.target.value }); }; _default.prototype.onAutosuggest = function onAutosuggest() { var value = this.state.value; var _marker = this._marker, offsetLeft = _marker.offsetLeft, offsetTop = _marker.offsetTop; var _getCurrentChunk = this.getCurrentChunk(value), chunk = _getCurrentChunk.chunk; var suggest = this.shouldAutosuggest(chunk); if (suggest) { this.setState({ dropdownClosed: false, dropdownOpen: true, dropdownValue: chunk, dropdownX: offsetLeft, dropdownY: offsetTop + 25 // line height + 5 extra padding }); } else { this.setState({ dropdownOpen: false }); } }; _default.prototype.onSelectValue = function onSelectValue(chunk) { var _this2 = this; var appended = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ''; var value = this.state.value; var _getCurrentChunk2 = this.getCurrentChunk(value), index = _getCurrentChunk2.index, indexEnd = _getCurrentChunk2.indexEnd; var before = value.slice(0, index); var after = value.slice(indexEnd); var position = index + chunk.length + appended.length; // const positionEnd = position + after.length this.setState({ value: '' + before + chunk + appended + after, dropdownClosed: appended !== ':' }, function () { // position caret at the end of the inserted value _this2._input.focus(); _this2._input.setSelectionRange(position, position); }); }; _default.prototype.handleEnterKey = function handleEnterKey(evt) { // whether this input is infocus var isFocused = document.activeElement === this._input; // submit on enter, line break on shift enter // dropdown handles enter key globally, so prevent clash if (!evt.shiftKey && isFocused && !this.state.dropdownOpen) { evt.preventDefault(); this.props.onSubmit(this.state.value); } }; _default.prototype.shouldAutosuggest = function shouldAutosuggest(chunk) { var selectionStart = this._input.selectionStart; var _state2 = this.state, value = _state2.value, focused = _state2.focused; // next character is whitespace, closing paren or null var nextCharIsEmpty = !value.charAt(selectionStart) || /[)\s]/.test(value.charAt(selectionStart)); // whitespace/negation/paren before and whitespace after caret var isNewWord = nextCharIsEmpty && /[\s-(]/.test(value.charAt(selectionStart - 1)); // cursor is at end of the current word var atEndOfWord = nextCharIsEmpty && /[^)\s]/.test(value.charAt(selectionStart - 1)); return focused && (!value || isNewWord || atEndOfWord && !this.state.dropdownClosed); }; _default.prototype.onClose = function onClose(forWord) { this.setState({ dropdownOpen: false, // don't reopen if it was closed for current word dropdownClosed: forWord || false }); }; // onClickToken (start, end) { // // move cursor to end of token // this._input.focus() // this._input.setSelectionRange(end, end) // } _default.prototype.extract = function extract(value) { var nameKeyIncludes = this.props.nameKeyIncludes; var attributes = this.state.attributes; return (0, _token.extractTokens)(value, attributes, nameKeyIncludes); }; _default.prototype.getCurrentChunk = function getCurrentChunk(value) { var selectionStart = this._input.selectionStart; // get location of each token found in value var tokens = this.extract(value); // find index of the closest previous whitespace var prevStr = value.substring(0, selectionStart); var prevMatch = prevStr.match(/[^\s]*$/); var prevIdx = prevMatch ? prevStr.lastIndexOf(prevMatch[prevMatch.length - 1]) : -1; // determine correct index for the start of the chunk var index = prevIdx; for (var _iterator = tokens.reverse(), _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { var _ref2; if (_isArray) { if (_i >= _iterator.length) break; _ref2 = _iterator[_i++]; } else { _i = _iterator.next(); if (_i.done) break; _ref2 = _i.value; } var _ref = _ref2; var start = _ref[0]; var end = _ref[1]; // token is between whitespace and cursor if (selectionStart > end && prevIdx < start) { index = end; break; } // at the end of or inside a token (thats what she said) if (selectionStart > start && selectionStart <= end) { index = start; break; } // there is whitespace in the token if (prevIdx > start && prevIdx < end) { index = end; break; } } // value is result of cursor back to beginning of chunk var chunk = value.substring(index, selectionStart); var indexEnd = index + chunk.length; return { index: index, indexEnd: indexEnd, chunk: chunk }; }; _default.prototype.buildTokens = function buildTokens(value) { var _this3 = this; var relativeToIdx = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; var chunks = []; var positions = this.extract(value); var currentPosition = 0; positions.reduce(function (prev, next) { // const startIdx = next[0] + relativeToIdx // const endIdx = next[1] + relativeToIdx chunks.push(value.substring(prev[1], next[0])); chunks.push(_react2.default.createElement( _index.Token, { key: 'token-' + next[0], tokenColor: _this3.props.inputProps.tokenColor }, value.substring(next[0], next[1]) )); currentPosition = next[1]; return next; }, [null, 0]); chunks.push(value.substring(currentPosition)); return chunks.filter(Boolean); }; _default.prototype.buildOverlay = function buildOverlay(value) { var _this4 = this; // figure out where we should split the overlay, // so we know where to position the dropdown var _getCurrentChunk3 = this.getCurrentChunk(value), index = _getCurrentChunk3.index; // everything to the left of the current word/token var stuffOnLeft = this.buildTokens(value.substring(0, index)); // everything to the right of the current word/token // need to have default whitespace or dropdown will not find position of caret var stuffOnRight = this.buildTokens(value.substring(index) || ' ', index); // since it will never split up a token, // we can build each side of cursor independently return [stuffOnLeft, _react2.default.createElement( _index.Inline, { key: 'after-' + index, style: { outline: this.props.debug ? '1px solid red' : 'none' }, innerRef: function innerRef(ref) { return _this4._marker = ref; } }, stuffOnRight )]; }; _default.prototype.render = function render() { var _this5 = this; var _props = this.props, nameKey = _props.nameKey, className = _props.className, inputProps = _props.inputProps, placeholder = _props.placeholder, keyboardHelpers = _props.keyboardHelpers, collapseOnBlur = _props.collapseOnBlur, footerComponent = _props.footerComponent, dropdownProps = _props.dropdownProps, selectorProps = _props.selectorProps, listProps = _props.listProps; var _state3 = this.state, value = _state3.value, attributes = _state3.attributes, dropdownOpen = _state3.dropdownOpen, dropdownValue = _state3.dropdownValue, dropdownX = _state3.dropdownX, dropdownY = _state3.dropdownY, overlayComponents = _state3.overlayComponents; var collapsed = !this.state.focused && collapseOnBlur; return _react2.default.createElement( _reactPageClick2.default, { outsideOnly: true, notify: this.onClose }, _react2.default.createElement( _index.Container, { className: className }, _react2.default.createElement( _index.InputContainer, _extends({}, inputProps, { onClick: function onClick() { return _this5._input.focus(); } }), _react2.default.createElement( _index.Overlay, { collapsed: collapsed }, overlayComponents ), _react2.default.createElement(_index.Input, { autoComplete: 'off', autoCorrect: 'off', autoCapitalize: 'off', spellCheck: 'false', autoFocus: inputProps.autoFocus, maxRows: collapsed ? 1 : undefined, placeholder: placeholder, placeholderColor: inputProps.placeholderColor, value: value, onFocus: this.onFocus, onBlur: this.onBlur, onKeyDown: this.onKeyDown, onChange: this.onChange, inputRef: function inputRef(ref) { return _this5._input = ref; } }) ), dropdownOpen && _react2.default.createElement(_dropdown2.default, { keyboardHelpers: keyboardHelpers, footerComponent: footerComponent, attributes: attributes, value: dropdownValue, nameKey: nameKey, onSelect: this.onSelectValue, onClose: this.onClose, offsetX: dropdownX, offsetY: dropdownY, dropdownProps: dropdownProps, selectorProps: selectorProps, listProps: listProps }) ) ); }; return _default; }(_react.Component), _class.propTypes = { // eslint-disable-line debug: _propTypes2.default.bool, data: _propTypes2.default.array, nameKey: _propTypes2.default.string, nameKeyIncludes: _propTypes2.default.array, defaultValue: _propTypes2.default.string, placeholder: _propTypes2.default.string, onChange: _propTypes2.default.func, onSubmit: _propTypes2.default.func, keyboardHelpers: _propTypes2.default.bool, collapseOnBlur: _propTypes2.default.bool, footerComponent: _propTypes2.default.func, inputProps: _propTypes2.default.object, dropdownProps: _propTypes2.default.object, selectorProps: _propTypes2.default.object, listProps: _propTypes2.default.object }, _class.defaultProps = { // eslint-disable-line data: [], nameKey: 'name', nameKeyIncludes: ['name'], defaultValue: '', onChange: function onChange() {}, onSubmit: function onSubmit() {}, placeholder: 'Search', inputProps: {}, dropdownProps: {}, selectorProps: {}, listProps: {} }, _temp); exports.default = _default; module.exports = exports['default'];