From 6dd9acebc1defb19db17f4eb4a2a868dbbcbb15e Mon Sep 17 00:00:00 2001 From: Zach Tratar Date: Wed, 29 Jun 2016 12:09:36 -0700 Subject: [PATCH 1/3] Wrap onSelect with event --- lib/Autocomplete.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/Autocomplete.js b/lib/Autocomplete.js index 05dcc5af..997ced5b 100644 --- a/lib/Autocomplete.js +++ b/lib/Autocomplete.js @@ -175,7 +175,7 @@ let Autocomplete = React.createClass({ value.length, value.length ) - this.props.onSelect(value, item) + this.props.onSelect(value, item, event) }) } }, @@ -250,13 +250,13 @@ let Autocomplete = React.createClass({ this.setState({ highlightedIndex: index }) }, - selectItemFromMouse (item) { + selectItemFromMouse (item, event) { var value = this.props.getItemValue(item); this.setState({ isOpen: false, highlightedIndex: null }, () => { - this.props.onSelect(value, item) + this.props.onSelect(value, item, event) this.refs.input.focus() this.setIgnoreBlur(false) }) @@ -276,7 +276,7 @@ let Autocomplete = React.createClass({ return React.cloneElement(element, { onMouseDown: () => this.setIgnoreBlur(true), onMouseEnter: () => this.highlightItemFromMouse(index), - onClick: () => this.selectItemFromMouse(item), + onClick: (event) => this.selectItemFromMouse(item, event), ref: `item-${index}`, }) }) From a3dff1b483c22105eaf64943e6632b9929682641 Mon Sep 17 00:00:00 2001 From: Zach Tratar Date: Wed, 29 Jun 2016 13:04:21 -0700 Subject: [PATCH 2/3] Add onFocus and onBlur passthroughs --- lib/Autocomplete.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/Autocomplete.js b/lib/Autocomplete.js index 997ced5b..e1d06be5 100644 --- a/lib/Autocomplete.js +++ b/lib/Autocomplete.js @@ -7,6 +7,8 @@ let Autocomplete = React.createClass({ propTypes: { value: React.PropTypes.any, + onFocus: React.PropTypes.func, + onBlur: React.PropTypes.func, onChange: React.PropTypes.func, onSelect: React.PropTypes.func, shouldItemRender: React.PropTypes.func, @@ -28,6 +30,8 @@ let Autocomplete = React.createClass({ }, inputProps: {}, onChange () {}, + onFocus () {}, + onBlur () {}, onSelect (value, item) {}, renderMenu (items, value, style) { return
@@ -290,6 +294,7 @@ let Autocomplete = React.createClass({ }, handleInputBlur () { + this.props.onBlur({ ignoreBlur: this._ignoreBlur }); if (this._ignoreBlur) return this.setState({ @@ -299,6 +304,7 @@ let Autocomplete = React.createClass({ }, handleInputFocus () { + this.props.onFocus({ ignoreBlur: this._ignoreBlur }}); if (this._ignoreBlur) return this.setState({ isOpen: true }) From 8ce9079812d6e0e706af4b45fbb10882056391f3 Mon Sep 17 00:00:00 2001 From: Zach Tratar Date: Wed, 29 Jun 2016 13:22:32 -0700 Subject: [PATCH 3/3] Updated autocomplete for building --- build/lib/Autocomplete.js | 58 +++++++++------------- build/lib/__tests__/Autocomplete-test.js | 63 +++++++++++++----------- build/lib/utils.js | 6 +-- lib/Autocomplete.js | 2 +- 4 files changed, 60 insertions(+), 69 deletions(-) diff --git a/build/lib/Autocomplete.js b/build/lib/Autocomplete.js index fd6cc923..83d048a9 100644 --- a/build/lib/Autocomplete.js +++ b/build/lib/Autocomplete.js @@ -1,5 +1,7 @@ 'use strict'; +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 _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 React = require('react'); @@ -10,8 +12,11 @@ var _debugStates = []; var Autocomplete = React.createClass({ displayName: 'Autocomplete', + propTypes: { value: React.PropTypes.any, + onFocus: React.PropTypes.func, + onBlur: React.PropTypes.func, onChange: React.PropTypes.func, onSelect: React.PropTypes.func, shouldItemRender: React.PropTypes.func, @@ -33,6 +38,8 @@ var Autocomplete = React.createClass({ }, inputProps: {}, onChange: function onChange() {}, + onFocus: function onFocus() {}, + onBlur: function onBlur() {}, onSelect: function onSelect(value, item) {}, renderMenu: function renderMenu(items, value, style) { return React.createElement('div', { style: _extends({}, style, this.menuStyle), children: items }); @@ -40,6 +47,7 @@ var Autocomplete = React.createClass({ shouldItemRender: function shouldItemRender() { return true; }, + menuStyle: { borderRadius: '3px', boxShadow: '0 2px 12px rgba(0, 0, 0, 0.1)', @@ -51,25 +59,20 @@ var Autocomplete = React.createClass({ maxHeight: '50%' } }; }, - - // TODO: don't cheat, let it flow to the bottom getInitialState: function getInitialState() { return { isOpen: false, highlightedIndex: null }; }, - componentWillMount: function componentWillMount() { this._ignoreBlur = false; this._performAutoCompleteOnUpdate = false; this._performAutoCompleteOnKeyUp = false; }, - componentWillReceiveProps: function componentWillReceiveProps() { this._performAutoCompleteOnUpdate = true; }, - componentDidUpdate: function componentDidUpdate(prevProps, prevState) { if (this.state.isOpen === true && prevState.isOpen === false) this.setMenuPositions(); @@ -80,7 +83,6 @@ var Autocomplete = React.createClass({ this.maybeScrollItemIntoView(); }, - maybeScrollItemIntoView: function maybeScrollItemIntoView() { if (this.state.isOpen === true && this.state.highlightedIndex !== null) { var itemNode = this.refs['item-' + this.state.highlightedIndex]; @@ -88,12 +90,11 @@ var Autocomplete = React.createClass({ scrollIntoView(itemNode, menuNode, { onlyScrollIfNeeded: true }); } }, - handleKeyDown: function handleKeyDown(event) { var _this = this; if (this.keyDownHandlers[event.key]) this.keyDownHandlers[event.key].call(this, event);else { - var _ret = (function () { + var _ret = function () { var _event$target = event.target; var selectionStart = _event$target.selectionStart; var value = _event$target.value; @@ -102,7 +103,7 @@ var Autocomplete = React.createClass({ // Nothing changed, no need to do anything. This also prevents // our workaround below from nuking user-made selections return { - v: undefined + v: void 0 }; _this.setState({ highlightedIndex: null, @@ -112,17 +113,15 @@ var Autocomplete = React.createClass({ // to work around a setSelectionRange bug in IE (#80) _this.refs.input.selectionStart = selectionStart; }); - })(); + }(); - if (typeof _ret === 'object') return _ret.v; + if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v; } }, - handleChange: function handleChange(event) { this._performAutoCompleteOnKeyUp = true; this.props.onChange(event, event.target.value); }, - handleKeyUp: function handleKeyUp() { if (this._performAutoCompleteOnKeyUp) { this._performAutoCompleteOnKeyUp = false; @@ -130,6 +129,7 @@ var Autocomplete = React.createClass({ } }, + keyDownHandlers: { ArrowDown: function ArrowDown(event) { event.preventDefault(); @@ -144,7 +144,6 @@ var Autocomplete = React.createClass({ isOpen: true }); }, - ArrowUp: function ArrowUp(event) { event.preventDefault(); var itemsLength = this.getFilteredItems().length; @@ -158,7 +157,6 @@ var Autocomplete = React.createClass({ isOpen: true }); }, - Enter: function Enter(event) { var _this2 = this; @@ -183,11 +181,10 @@ var Autocomplete = React.createClass({ }, function () { //this.refs.input.focus() // TODO: file issue _this2.refs.input.setSelectionRange(value.length, value.length); - _this2.props.onSelect(value, item); + _this2.props.onSelect(value, item, event); }); } }, - Escape: function Escape(event) { this.setState({ highlightedIndex: null, @@ -215,7 +212,6 @@ var Autocomplete = React.createClass({ return items; }, - maybeAutoCompleteText: function maybeAutoCompleteText() { var _this4 = this; @@ -236,7 +232,6 @@ var Autocomplete = React.createClass({ if (highlightedIndex === null) this.setState({ highlightedIndex: 0 }, setSelection);else setSelection(); } }, - setMenuPositions: function setMenuPositions() { var node = this.refs.input; var rect = node.getBoundingClientRect(); @@ -250,12 +245,10 @@ var Autocomplete = React.createClass({ menuWidth: rect.width + marginLeft + marginRight }); }, - highlightItemFromMouse: function highlightItemFromMouse(index) { this.setState({ highlightedIndex: index }); }, - - selectItemFromMouse: function selectItemFromMouse(item) { + selectItemFromMouse: function selectItemFromMouse(item, event) { var _this5 = this; var value = this.props.getItemValue(item); @@ -263,16 +256,14 @@ var Autocomplete = React.createClass({ isOpen: false, highlightedIndex: null }, function () { - _this5.props.onSelect(value, item); + _this5.props.onSelect(value, item, event); _this5.refs.input.focus(); _this5.setIgnoreBlur(false); }); }, - setIgnoreBlur: function setIgnoreBlur(ignore) { this._ignoreBlur = ignore; }, - renderMenu: function renderMenu() { var _this6 = this; @@ -285,8 +276,8 @@ var Autocomplete = React.createClass({ onMouseEnter: function onMouseEnter() { return _this6.highlightItemFromMouse(index); }, - onClick: function onClick() { - return _this6.selectItemFromMouse(item); + onClick: function onClick(event) { + return _this6.selectItemFromMouse(item, event); }, ref: 'item-' + index }); @@ -299,29 +290,26 @@ var Autocomplete = React.createClass({ var menu = this.props.renderMenu(items, this.props.value, style); return React.cloneElement(menu, { ref: 'menu' }); }, - handleInputBlur: function handleInputBlur() { + this.props.onBlur({ ignoreBlur: this._ignoreBlur }); if (this._ignoreBlur) return; this.setState({ isOpen: false, highlightedIndex: null }); }, - handleInputFocus: function handleInputFocus() { + this.props.onFocus({ ignoreBlur: this._ignoreBlur }); if (this._ignoreBlur) return; this.setState({ isOpen: true }); }, - isInputFocused: function isInputFocused() { var el = this.refs.input; return el.ownerDocument && el === el.ownerDocument.activeElement; }, - handleInputClick: function handleInputClick() { if (this.isInputFocused() && this.state.isOpen === false) this.setState({ isOpen: true });else if (this.state.highlightedIndex !== null) this.selectItemFromMouse(this.getFilteredItems()[this.state.highlightedIndex]); }, - render: function render() { var _this7 = this; @@ -343,13 +331,13 @@ var Autocomplete = React.createClass({ ref: 'input', onFocus: this.handleInputFocus, onBlur: this.handleInputBlur, - onChange: function (event) { + onChange: function onChange(event) { return _this7.handleChange(event); }, - onKeyDown: function (event) { + onKeyDown: function onKeyDown(event) { return _this7.handleKeyDown(event); }, - onKeyUp: function (event) { + onKeyUp: function onKeyUp(event) { return _this7.handleKeyUp(event); }, onClick: this.handleInputClick, diff --git a/build/lib/__tests__/Autocomplete-test.js b/build/lib/__tests__/Autocomplete-test.js index 17a8dc84..5bd71927 100644 --- a/build/lib/__tests__/Autocomplete-test.js +++ b/build/lib/__tests__/Autocomplete-test.js @@ -2,8 +2,6 @@ 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; }; -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } - var _react = require('react'); var _react2 = _interopRequireDefault(_react); @@ -38,20 +36,23 @@ var _Autocomplete2 = _interopRequireDefault(_Autocomplete); var _utils = require('../utils'); -var expect = _chai2['default'].expect; +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var expect = _chai2.default.expect; + -_chai2['default'].use((0, _chaiEnzyme2['default'])()); +_chai2.default.use((0, _chaiEnzyme2.default)()); function AutocompleteComponentJSX(extraProps) { - return _react2['default'].createElement(_Autocomplete2['default'], _extends({ + return _react2.default.createElement(_Autocomplete2.default, _extends({ labelText: 'Choose a state from the US', inputProps: { name: "US state" }, - getItemValue: function (item) { + getItemValue: function getItemValue(item) { return item.name; }, items: (0, _utils.getStates)(), - renderItem: function (item, isHighlighted) { - return _react2['default'].createElement( + renderItem: function renderItem(item, isHighlighted) { + return _react2.default.createElement( 'div', { style: isHighlighted ? _utils.styles.highlightedItem : _utils.styles.item, @@ -71,13 +72,13 @@ describe('Autocomplete acceptance tests', function () { it('should display autocomplete menu when input has focus', function () { - expect(autocompleteWrapper.state('isOpen')).to.be['false']; + expect(autocompleteWrapper.state('isOpen')).to.be.false; expect(autocompleteWrapper.instance().refs.menu).to.not.exist; // Display autocomplete menu upon input focus autocompleteInputWrapper.simulate('focus'); - expect(autocompleteWrapper.state('isOpen')).to.be['true']; + expect(autocompleteWrapper.state('isOpen')).to.be.true; expect(autocompleteWrapper.instance().refs.menu).to.exist; }); @@ -93,7 +94,7 @@ describe('Autocomplete acceptance tests', function () { autocompleteInputWrapper.simulate('blur'); - expect(autocompleteWrapper.state('isOpen')).to.be['false']; + expect(autocompleteWrapper.state('isOpen')).to.be.false; expect(autocompleteWrapper.instance().refs.menu).to.not.exist; }); }); @@ -110,7 +111,8 @@ describe('Autocomplete keyPress-> event handlers', function () { var value = ''; autocompleteWrapper.setProps({ value: value, onChange: function onChange(_, v) { value = v; - } }); + } + }); autocompleteInputWrapper.get(0).value = 'a'; autocompleteInputWrapper.simulate('keyPress', { key: 'a', keyCode: 97, which: 97 }); @@ -133,7 +135,7 @@ describe('Autocomplete kewDown->ArrowDown event handlers', function () { autocompleteInputWrapper.simulate('keyDown', { key: "ArrowDown", keyCode: 40, which: 40 }); - expect(autocompleteWrapper.state('isOpen')).to.be['true']; + expect(autocompleteWrapper.state('isOpen')).to.be.true; expect(autocompleteWrapper.state('highlightedIndex')).to.equal(0); }); @@ -147,7 +149,7 @@ describe('Autocomplete kewDown->ArrowDown event handlers', function () { autocompleteInputWrapper.simulate('keyDown', { key: "ArrowDown", keyCode: 40, which: 40 }); - expect(autocompleteWrapper.state('isOpen')).to.be['true']; + expect(autocompleteWrapper.state('isOpen')).to.be.true; expect(autocompleteWrapper.state('highlightedIndex')).to.equal(n + 1); }); @@ -160,7 +162,7 @@ describe('Autocomplete kewDown->ArrowDown event handlers', function () { autocompleteInputWrapper.simulate('keyDown', { key: "ArrowDown", keyCode: 40, which: 40 }); - expect(autocompleteWrapper.state('isOpen')).to.be['true']; + expect(autocompleteWrapper.state('isOpen')).to.be.true; expect(autocompleteWrapper.state('highlightedIndex')).to.equal(0); }); }); @@ -178,7 +180,7 @@ describe('Autocomplete kewDown->ArrowUp event handlers', function () { autocompleteInputWrapper.simulate('keyDown', { key: 'ArrowUp', keyCode: 38, which: 38 }); - expect(autocompleteWrapper.state('isOpen')).to.be['true']; + expect(autocompleteWrapper.state('isOpen')).to.be.true; expect(autocompleteWrapper.state('highlightedIndex')).to.equal(49); }); @@ -192,7 +194,7 @@ describe('Autocomplete kewDown->ArrowUp event handlers', function () { autocompleteInputWrapper.simulate('keyDown', { key: 'ArrowUp', keyCode: 38, which: 38 }); - expect(autocompleteWrapper.state('isOpen')).to.be['true']; + expect(autocompleteWrapper.state('isOpen')).to.be.true; expect(autocompleteWrapper.state('highlightedIndex')).to.equal(n - 1); }); @@ -205,7 +207,7 @@ describe('Autocomplete kewDown->ArrowUp event handlers', function () { autocompleteInputWrapper.simulate('keyDown', { key: 'ArrowUp', keyCode: 38, which: 38 }); - expect(autocompleteWrapper.state('isOpen')).to.be['true']; + expect(autocompleteWrapper.state('isOpen')).to.be.true; expect(autocompleteWrapper.state('highlightedIndex')).to.equal(49); }); }); @@ -218,7 +220,7 @@ describe('Autocomplete kewDown->Enter event handlers', function () { it('should do nothing if the menu is closed', function () { autocompleteWrapper.setState({ 'isOpen': false }); autocompleteWrapper.simulate('keyDown', { key: 'Enter', keyCode: 13, which: 13 }); - expect(autocompleteWrapper.state('isOpen')).to.be['false']; + expect(autocompleteWrapper.state('isOpen')).to.be.false; }); it('should close menu if input has focus but no item has been selected and then the Enter key is hit', function () { @@ -227,16 +229,17 @@ describe('Autocomplete kewDown->Enter event handlers', function () { autocompleteInputWrapper.simulate('focus'); autocompleteWrapper.setProps({ value: value, onSelect: function onSelect(v) { value = v; - } }); + } + }); // simulate keyUp of backspace, triggering autocomplete suggestion on an empty string, which should result in nothing highlighted autocompleteInputWrapper.simulate('keyUp', { key: 'Backspace', keyCode: 8, which: 8 }); - expect(autocompleteWrapper.state('highlightedIndex')).to.be['null']; + expect(autocompleteWrapper.state('highlightedIndex')).to.be.null; autocompleteInputWrapper.simulate('keyDown', { key: 'Enter', keyCode: 13, which: 13 }); expect(value).to.equal(''); - expect(autocompleteWrapper.state('isOpen')).to.be['false']; + expect(autocompleteWrapper.state('isOpen')).to.be.false; }); it('should invoke `onSelect` with the selected menu item and close the menu', function () { @@ -246,7 +249,8 @@ describe('Autocomplete kewDown->Enter event handlers', function () { autocompleteInputWrapper.simulate('focus'); autocompleteWrapper.setProps({ value: value, onSelect: function onSelect(v) { value = v; - } }); + } + }); // simulate keyUp of last key, triggering autocomplete suggestion + selection of the suggestion in the menu autocompleteInputWrapper.simulate('keyUp', { key: 'r', keyCode: 82, which: 82 }); @@ -254,10 +258,11 @@ describe('Autocomplete kewDown->Enter event handlers', function () { // Hit enter, updating state.value with the selected Autocomplete suggestion autocompleteInputWrapper.simulate('keyDown', { key: 'Enter', keyCode: 13, which: 13, preventDefault: function preventDefault() { defaultPrevented = true; - } }); + } + }); expect(value).to.equal('Arizona'); - expect(autocompleteWrapper.state('isOpen')).to.be['false']; - expect(defaultPrevented).to.be['true']; + expect(autocompleteWrapper.state('isOpen')).to.be.false; + expect(defaultPrevented).to.be.true; }); }); @@ -272,8 +277,8 @@ describe('Autocomplete kewDown->Escape event handlers', function () { autocompleteInputWrapper.simulate('keyDown', { key: 'Escape', keyCode: 27, which: 27 }); - expect(autocompleteWrapper.state('isOpen')).to.be['false']; - expect(autocompleteWrapper.state('highlightedIndex')).to.be['null']; + expect(autocompleteWrapper.state('isOpen')).to.be.false; + expect(autocompleteWrapper.state('highlightedIndex')).to.be.null; }); }); @@ -299,7 +304,7 @@ describe('Autocomplete click event handlers', function () { // Click inside input, updating state.value with the selected Autocomplete suggestion autocompleteInputWrapper.simulate('click'); expect(value).to.equal('Arizona'); - expect(autocompleteWrapper.state('isOpen')).to.be['false']; + expect(autocompleteWrapper.state('isOpen')).to.be.false; }); }); diff --git a/build/lib/utils.js b/build/lib/utils.js index a6adc6ec..627cb888 100644 --- a/build/lib/utils.js +++ b/build/lib/utils.js @@ -1,13 +1,13 @@ 'use strict'; -Object.defineProperty(exports, '__esModule', { +Object.defineProperty(exports, "__esModule", { value: true }); exports.matchStateToTerm = matchStateToTerm; exports.sortStates = sortStates; exports.fakeRequest = fakeRequest; exports.getStates = getStates; -var styles = { +var styles = exports.styles = { item: { padding: '2px 6px', cursor: 'default' @@ -25,8 +25,6 @@ var styles = { } }; -exports.styles = styles; - function matchStateToTerm(state, value) { return state.name.toLowerCase().indexOf(value.toLowerCase()) !== -1 || state.abbr.toLowerCase().indexOf(value.toLowerCase()) !== -1; } diff --git a/lib/Autocomplete.js b/lib/Autocomplete.js index e1d06be5..20f17c33 100644 --- a/lib/Autocomplete.js +++ b/lib/Autocomplete.js @@ -304,7 +304,7 @@ let Autocomplete = React.createClass({ }, handleInputFocus () { - this.props.onFocus({ ignoreBlur: this._ignoreBlur }}); + this.props.onFocus({ ignoreBlur: this._ignoreBlur }); if (this._ignoreBlur) return this.setState({ isOpen: true })