diff --git a/src/components/combo/combo.spec.ts b/src/components/combo/combo.spec.ts index c22932bbf..03ded9857 100644 --- a/src/components/combo/combo.spec.ts +++ b/src/components/combo/combo.spec.ts @@ -17,6 +17,7 @@ import { first } from '../common/util.js'; import { createFormAssociatedTestBed, isFocused, + simulateBlur, simulateClick, simulateKeyboard, simulatePointerDown, @@ -1417,6 +1418,89 @@ describe('Combo', () => { // The dropdown should remain open expect(combo.open).to.be.true; }); + + it('issue 2221 - invalid state when tabbing out of a single select combo', async () => { + combo.singleSelect = true; + await combo.show(); + await list.layoutComplete; + + // Has matches, selects first on tab out + await filterCombo('sof'); + expect(items(combo)).lengthOf(1); + + simulateKeyboard(input, tabKey); + await elementUpdated(combo); + + expect(combo.open).to.be.false; + expect(combo.value).to.eql(['BG01']); + + combo.deselect(); + await combo.show(); + await list.layoutComplete; + + // No matches, should clear value on tab out + await filterCombo('xxx'); + expect(items(combo)).to.be.empty; + + simulateKeyboard(input, tabKey); + await elementUpdated(combo); + + expect(combo.open).to.be.false; + expect(combo.value).to.be.empty; + }); + + it('issue 2221 - tabbing out with an existing selection preserves the selection', async () => { + combo.singleSelect = true; + combo.select('BG01'); + await elementUpdated(combo); + + expect(input.value).to.equal('Sofia'); + + await combo.show(); + await list.layoutComplete; + + simulateKeyboard(input, tabKey); + await elementUpdated(combo); + + expect(combo.open).to.be.false; + expect(combo.value).to.eql(['BG01']); + expect(input.value).to.equal('Sofia'); + }); + + it('issue 2221 - clicking outside with partial text and no selection clears the input', async () => { + combo.singleSelect = true; + await combo.show(); + await list.layoutComplete; + + await filterCombo('sof'); + expect(items(combo)).lengthOf(1); + + // Simulate click outside by dispatching blur without confirming a selection + simulateBlur(combo); + await elementUpdated(combo); + + expect(combo.value).to.be.empty; + expect(input.value).to.equal(''); + }); + + it('issue 2221 - clicking outside after partial search over existing selection clears the input', async () => { + combo.singleSelect = true; + combo.select('BG01'); + await elementUpdated(combo); + + expect(input.value).to.equal('Sofia'); + + // Typing clears the existing selection immediately + await filterCombo('sof'); + expect(combo.value).to.be.empty; + + // Clicking outside (blur) should clear the partial text + simulateBlur(combo); + await elementUpdated(combo); + + expect(combo.value).to.be.empty; + expect(input.value).to.equal(''); + }); }); describe('Form integration', () => { diff --git a/src/components/combo/combo.ts b/src/components/combo/combo.ts index 0b0e08409..0b2c54dc1 100644 --- a/src/components/combo/combo.ts +++ b/src/components/combo/combo.ts @@ -643,6 +643,12 @@ export default class IgcComboComponent< return canClose; } + private _setSingleSelectionDisplayValue(value: string): void { + if (this.singleSelect && this._inputRef.value) { + this._inputRef.value.value = value; + } + } + //#endregion // #region Selection helpers @@ -817,10 +823,10 @@ export default class IgcComboComponent< this._displayValue = this._getValues(this._selected, this.displayKey).join( ', ' ); - this._formValue.setValueAndFormState(values); if (!initial) { + this._setSingleSelectionDisplayValue(this._displayValue); this._validate(); this._listRef.value?.requestUpdate(); } @@ -891,6 +897,7 @@ export default class IgcComboComponent< protected override _handleBlur(): void { if (isEmpty(this._selected)) { this._searchTerm = ''; + this._setSingleSelectionDisplayValue(''); } super._handleBlur(); } diff --git a/src/components/combo/controllers/navigation.ts b/src/components/combo/controllers/navigation.ts index 85f1f02b2..d602e3076 100644 --- a/src/components/combo/controllers/navigation.ts +++ b/src/components/combo/controllers/navigation.ts @@ -151,6 +151,11 @@ export class ComboNavigationController { // before the Shift+Tab behavior kicks in. this._host.focus(); } + + if (this._host.singleSelect && this.active > -1) { + this._config.interactions.select(this.active); + } + await this._config.interactions.hide(); } };