diff --git a/packages/combo-box/src/vaadin-combo-box-mixin.js b/packages/combo-box/src/vaadin-combo-box-mixin.js index 9bd16b691a4..bcddb3eddf3 100644 --- a/packages/combo-box/src/vaadin-combo-box-mixin.js +++ b/packages/combo-box/src/vaadin-combo-box-mixin.js @@ -698,6 +698,7 @@ export const ComboBoxMixin = (subclass) => this.selectedItem = null; if (this.allowCustomValue) { + delete this._lastCustomValue; this.value = ''; } } else { @@ -714,17 +715,27 @@ export const ComboBoxMixin = (subclass) => // to prevent a repetitive input value being saved after pressing ESC and Tab. !itemMatchingByLabel ) { + const customValue = this._inputElementValue; + + // User's logic in `custom-value-set` event listener might cause input to blur, + // which will result in attempting to commit the same custom value once again. + if (this._lastCustomValue === customValue) { + return; + } + + // Store reference to the last custom value for checking it + this._lastCustomValue = customValue; + // An item matching by label was not found, but custom values are allowed. // Dispatch a custom-value-set event with the input value. const e = new CustomEvent('custom-value-set', { - detail: this._inputElementValue, + detail: customValue, composed: true, cancelable: true, bubbles: true }); this.dispatchEvent(e); if (!e.defaultPrevented) { - const customValue = this._inputElementValue; this._selectItemForValue(customValue); this.value = customValue; } diff --git a/packages/combo-box/test/basic.test.js b/packages/combo-box/test/basic.test.js index 900f27043c1..ce2f611c6cf 100644 --- a/packages/combo-box/test/basic.test.js +++ b/packages/combo-box/test/basic.test.js @@ -271,6 +271,59 @@ describe('Properties', () => { expect(spy.calledOnce).to.be.true; }); + + it('should not fire twice when the custom value set listener causes blur', () => { + const spy = sinon.spy(); + comboBox.addEventListener('custom-value-set', spy); + + // Emulate opening the overlay that causes blur + comboBox.addEventListener('custom-value-set', () => { + comboBox.blur(); + }); + + comboBox.open(); + input.value = 'foo'; + input.dispatchEvent(new CustomEvent('input')); + comboBox.close(); + + expect(spy.calledOnce).to.be.true; + }); + + it('should fire twice when another custom value is committed by the user', () => { + const spy = sinon.spy(); + comboBox.addEventListener('custom-value-set', spy); + + comboBox.open(); + input.value = 'foo'; + input.dispatchEvent(new CustomEvent('input')); + comboBox.close(); + + input.value = 'bar'; + input.dispatchEvent(new CustomEvent('input')); + focusout(input); + + expect(spy.calledTwice).to.be.true; + }); + + it('should fire when setting the same custom value after clearing', () => { + const spy = sinon.spy(); + comboBox.addEventListener('custom-value-set', spy); + + input.value = 'foo'; + input.dispatchEvent(new CustomEvent('input')); + focusout(input); + + input.value = ''; + input.dispatchEvent(new CustomEvent('input')); + focusout(input); + + spy.resetHistory(); + input.value = 'foo'; + input.dispatchEvent(new CustomEvent('input')); + focusout(input); + + expect(spy.calledOnce).to.be.true; + }); }); });