From ee9da6cecba4f44a4434ecac73f6cb46c3d53690 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Tue, 11 Dec 2018 15:14:37 -0800 Subject: [PATCH] Reinstate combobox onblur pill creation in #1353 and fix bug in initial implementation (#1364) * Don't call onCreateOption callback when input is empty. --- CHANGELOG.md | 7 ++- src/components/combo_box/combo_box.js | 30 ++++++++++-- src/components/combo_box/combo_box.test.js | 57 ++++++++++++++++++++++ 3 files changed, 89 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0818f913767..10be84a2a0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ ## [`master`](https://github.com/elastic/eui/tree/master) +- Reinstate ([#1353](https://github.com/elastic/eui/pull/1353)) `onBlur` action on `EuiComboBox` ([#1364](https://github.com/elastic/eui/pull/1364)) + +**Bug fixes** + +- Fixed `onCreateOption` callback of `EuiComboBox` so it isn't called when the input is empty ([#1364](https://github.com/elastic/eui/pull/1364)) - Added `anchorClassName` prop to `EuiPopover` ([#1367](https://github.com/elastic/eui/pull/1367)) - Added support for `fullWidth` on `EuiSuperSelect` ([#1367](https://github.com/elastic/eui/pull/1367)) - Applied new scrollbar customization for Firefox ([#1367](https://github.com/elastic/eui/pull/1367)) @@ -16,7 +21,7 @@ **Bug fixes** - `react-datepicker` set milliseconds to zero when selecting time ([#1361](https://github.com/elastic/eui/pull/1361)) -- Revert ([#1353](https://github.com/elastic/eui/pull/1353)) `onBlur` action on `euiComboBox`. It caused regressions on Kibana. ([#1363](https://github.com/elastic/eui/pull/1363)) +- Revert ([#1353](https://github.com/elastic/eui/pull/1353)) `onBlur` action on `EuiComboBox`. It caused regressions on Kibana. ([#1363](https://github.com/elastic/eui/pull/1363)) ## [`5.5.1`](https://github.com/elastic/eui/tree/v5.5.1) diff --git a/src/components/combo_box/combo_box.js b/src/components/combo_box/combo_box.js index c81d2f3170b..3989c9c557c 100644 --- a/src/components/combo_box/combo_box.js +++ b/src/components/combo_box/combo_box.js @@ -204,22 +204,38 @@ export class EuiComboBox extends Component { }; addCustomOption = () => { + const { + options, + selectedOptions, + onCreateOption, + } = this.props; + + const { + searchValue, + matchingOptions, + } = this.state; + if (this.doesSearchMatchOnlyOption()) { - this.onAddOption(this.state.matchingOptions[0]); + this.onAddOption(matchingOptions[0]); + return; + } + + if (!onCreateOption) { return; } - if (!this.props.onCreateOption) { + // Don't bother trying to create an option if the user hasn't typed anything. + if (!searchValue) { return; } // Don't create the value if it's already been selected. - if (getSelectedOptionForSearchValue(this.state.searchValue, this.props.selectedOptions)) { + if (getSelectedOptionForSearchValue(searchValue, selectedOptions)) { return; } // Add new custom pill if this is custom input, even if it partially matches an option.. - const isOptionCreated = this.props.onCreateOption(this.state.searchValue, flattenOptionGroups(this.props.options)); + const isOptionCreated = this.props.onCreateOption(searchValue, flattenOptionGroups(options)); // Expect the consumer to be explicit in rejecting a custom option. if (isOptionCreated === false) { @@ -258,6 +274,12 @@ export class EuiComboBox extends Component { this.closeList(); } + // If the user tabs away or changes focus to another element, take whatever input they've + // typed and convert it into a pill, to prevent the combo box from looking like a text input. + if (!this.hasActiveOption() && !focusedInInput) { + this.addCustomOption(); + } + if (this.props.onBlur) { this.props.onBlur(e); } diff --git a/src/components/combo_box/combo_box.test.js b/src/components/combo_box/combo_box.test.js index da2a636adf5..fceb9d55682 100644 --- a/src/components/combo_box/combo_box.test.js +++ b/src/components/combo_box/combo_box.test.js @@ -135,6 +135,44 @@ describe('props', () => { }); describe('behavior', () => { + describe('hitting "Enter"', () => { + test(`calls the onCreateOption callback when there is input`, () => { + const onCreateOptionHandler = sinon.spy(); + + const component = mount( + + ); + + component.setState({ searchValue: 'foo' }); + const searchInput = findTestSubject(component, 'comboBoxSearchInput'); + searchInput.simulate('focus'); + searchInput.simulate('keyDown', { keyCode: comboBoxKeyCodes.ENTER }); + sinon.assert.calledOnce(onCreateOptionHandler); + sinon.assert.calledWith(onCreateOptionHandler, 'foo'); + }); + + test(`doesn't the onCreateOption callback when there is no input`, () => { + const onCreateOptionHandler = sinon.spy(); + + const component = mount( + + ); + + const searchInput = findTestSubject(component, 'comboBoxSearchInput'); + searchInput.simulate('focus'); + searchInput.simulate('keyDown', { keyCode: comboBoxKeyCodes.ENTER }); + sinon.assert.notCalled(onCreateOptionHandler); + }); + }); + describe('tabbing', () => { test(`off the search input closes the options list if the user isn't navigating the options`, () => { const onKeyDownWrapper = jest.fn(); @@ -160,6 +198,25 @@ describe('behavior', () => { expect(onKeyDownWrapper.mock.calls.length).toBe(1); }); + test(`off the search input calls onCreateOption`, () => { + const onCreateOptionHandler = sinon.spy(); + + const component = mount( + + ); + + component.setState({ searchValue: 'foo' }); + const searchInput = findTestSubject(component, 'comboBoxSearchInput'); + searchInput.simulate('focus'); + searchInput.simulate('blur'); + sinon.assert.calledOnce(onCreateOptionHandler); + sinon.assert.calledWith(onCreateOptionHandler, 'foo'); + }); + test('off the search input does nothing if the user is navigating the options', () => { const onKeyDownWrapper = jest.fn(); const component = mount(