From 8eac29efd70dbd83c3ae2147f5bfedf04523c81a Mon Sep 17 00:00:00 2001 From: Shawn Stern Date: Sun, 1 Apr 2018 15:17:14 -0700 Subject: [PATCH 1/3] Enabling optional `selectOnTab` prop. When enabled, Tab functions idenitcally to the Enter key for selecting menu items --- lib/Autocomplete.js | 83 ++++++++++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 34 deletions(-) diff --git a/lib/Autocomplete.js b/lib/Autocomplete.js index 1658c8bb..1efd01f9 100644 --- a/lib/Autocomplete.js +++ b/lib/Autocomplete.js @@ -142,6 +142,10 @@ class Autocomplete extends React.Component { * `` loses focus. */ selectOnBlur: PropTypes.bool, + /** + * Whether or not to select the highlighted item on a 'Tab' keypress + */ + selectOnTab: PropTypes.bool, /** * Arguments: `isOpen: Boolean` * @@ -187,6 +191,7 @@ class Autocomplete extends React.Component { }, autoHighlight: true, selectOnBlur: false, + selectOnTab: false, onMenuVisibilityChange() {}, } @@ -323,37 +328,7 @@ class Autocomplete extends React.Component { Enter(event) { // Key code 229 is used for selecting items from character selectors (Pinyin, Kana, etc) if (event.keyCode !== 13) return - // In case the user is currently hovering over the menu - this.setIgnoreBlur(false) - if (!this.isOpen()) { - // menu is closed so there is no selection to accept -> do nothing - return - } - else if (this.state.highlightedIndex == null) { - // input has focus but no menu item is selected + enter is hit -> close the menu, highlight whatever's in input - this.setState({ - isOpen: false - }, () => { - this.refs.input.select() - }) - } - else { - // text entered + menu item has been highlighted + enter is hit -> update value to that of selected menu item, close the menu - event.preventDefault() - const item = this.getFilteredItems(this.props)[this.state.highlightedIndex] - const value = this.props.getItemValue(item) - this.setState({ - isOpen: false, - highlightedIndex: null - }, () => { - //this.refs.input.focus() // TODO: file issue - this.refs.input.setSelectionRange( - value.length, - value.length - ) - this.props.onSelect(value, item) - }) - } + this.handleKeyboardSelection(event) }, Escape() { @@ -365,12 +340,52 @@ class Autocomplete extends React.Component { }) }, - Tab() { - // In case the user is currently hovering over the menu - this.setIgnoreBlur(false) + Tab(event) { + if (this.props.selectOnTab) { + this.handleKeyboardSelection(event) + } }, } + handleKeyboardSelection(event) { + // Destructure key value from event object for possible later use + const { key } = event + // In case the user is currently hovering over the menu + this.setIgnoreBlur(false) + if (!this.isOpen()) { + // menu is closed so there is no selection to accept -> do nothing + return + } + else if (this.state.highlightedIndex == null) { + // input has focus but no menu item is selected + Enter/Tab is hit -> close the menu, highlight whatever's in input + this.setState({ + isOpen: false + }, () => { + this.refs.input.select() + }) + } + else { + // text entered + menu item has been highlighted + Enter/Tab is hit -> update value to that of selected menu item, close the menu + if (key === 'Enter') { + // Prevent default on Enter, but we *do* want it on Tab events so user selection goes to next field if possible + event.preventDefault() + } + const item = this.getFilteredItems(this.props)[this.state.highlightedIndex] + const value = this.props.getItemValue(item) + this.setState({ + isOpen: false, + highlightedIndex: null + }, () => { + //this.refs.input.focus() // TODO: file issue + this.refs.input.setSelectionRange( + value.length, + value.length + ) + this.props.onSelect(value, item) + }) + } + } + getFilteredItems(props) { let items = props.items From ad02a9c2a150d427dafe5e15579f959c9faee2a2 Mon Sep 17 00:00:00 2001 From: Shawn Stern Date: Sun, 1 Apr 2018 15:21:17 -0700 Subject: [PATCH 2/3] Adding test suite for `selectOnTab`. Test coverage at 100%. --- lib/__tests__/Autocomplete-test.js | 44 ++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/lib/__tests__/Autocomplete-test.js b/lib/__tests__/Autocomplete-test.js index ed5b79fb..ac2a249c 100644 --- a/lib/__tests__/Autocomplete-test.js +++ b/lib/__tests__/Autocomplete-test.js @@ -596,6 +596,50 @@ describe('Autocomplete keyDown->Escape event handlers', () => { }) +describe('Autocomplete kewDown->Tab event handlers', () => { + it('should invoke `onSelect` with the selected menu item and close the menu when selectOnTab is true', () => { + const autocompleteWrapper = mount(AutocompleteComponentJSX({ + selectOnTab: true, + })) + const autocompleteInputWrapper = autocompleteWrapper.find('input') + + let value = 'Ar' + let defaultPrevented = false + autocompleteWrapper.setState({ 'isOpen': true }) + autocompleteInputWrapper.simulate('focus') + autocompleteWrapper.setProps({ value, 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 }) + + // Hit tab, updating state.value with the selected Autocomplete suggestion + autocompleteInputWrapper.simulate('keyDown', { key : 'Tab', keyCode: 9, which: 9, preventDefault() { defaultPrevented = true } }) + expect(value).toEqual('Arizona') + expect(autocompleteWrapper.state('isOpen')).toBe(false) + expect(defaultPrevented).toBe(false) + }) + + it('should not do anything if selectOnTab is false', () => { + const autocompleteWrapper = mount(AutocompleteComponentJSX({ + selectOnTab: false, + })) + const autocompleteInputWrapper = autocompleteWrapper.find('input') + + let value = 'Ar' + autocompleteWrapper.setState({ 'isOpen': true }) + autocompleteInputWrapper.simulate('focus') + autocompleteWrapper.setProps({ value, 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 }) + + // Pressing tab should not change the state of the component + autocompleteInputWrapper.simulate('keyDown', { key : 'Tab', keyCode: 9, which: 9 }) + expect(value).toEqual('Ar') + expect(autocompleteWrapper.state('isOpen')).toBe(true) + }) +}) + describe('Autocomplete keyDown', () => { it('should not clear highlightedIndex for keys that don\'t modify `input.value`', () => { const tree = mount(AutocompleteComponentJSX({ open: true })) From b7c2844d04277be1ff2323c04f11c6a2b5c26b80 Mon Sep 17 00:00:00 2001 From: Shawn Stern Date: Sun, 1 Apr 2018 15:32:52 -0700 Subject: [PATCH 3/3] Updating README --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index e78523da..5125c067 100644 --- a/README.md +++ b/README.md @@ -173,6 +173,12 @@ Default value: `false` Whether or not to automatically select the highlighted item when the `` loses focus. +#### `selectOnTab: Boolean` (optional) +Default value: `false` + +Whether or not to select the highlighted item when the +Tab key is pressed (same as existing Enter or Click behavior). + #### `shouldItemRender: Function` (optional) Arguments: `item: Any, value: String`