-
Notifications
You must be signed in to change notification settings - Fork 247
fix: update combobox to handle duplicate items #5950
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| --- | ||
| '@spectrum-web-components/combobox': minor | ||
| --- | ||
|
|
||
| **Added**: Enhanced `<sp-combobox>` change event to include both `value` and `itemText` in the event detail, enabling consumers to access both the unique identifier and display text of the selected option. | ||
|
|
||
| **Fixed**: Resolved issue where selecting an option would incorrectly match the first option with the same display text. The component now correctly preserves the selected option's value even when multiple options share similar display text. | ||
|
|
||
| ```js | ||
| combobox.addEventListener('change', (event) => { | ||
| const { value, itemText } = event.detail; | ||
| console.log(`Selected: ${itemText} (ID: ${value})`); | ||
| }); | ||
| ``` |
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -52,6 +52,7 @@ export type ComboboxOption = { | |||
| * @element sp-combobox | ||||
| * @slot - Supply Menu Item elements to the default slot in order to populate the available options | ||||
| * @slot tooltip - Tooltip to to be applied to the the Picker Button | ||||
| * @fires change - Announces that a selection has been made. The event detail contains `value` and `itemText` of the selected option. | ||||
| */ | ||||
| export class Combobox extends Textfield { | ||||
| public static override get styles(): CSSResultArray { | ||||
|
|
@@ -105,6 +106,13 @@ export class Combobox extends Textfield { | |||
|
|
||||
| private itemValue = ''; | ||||
|
|
||||
| /** | ||||
| * Tracks the value when an item is selected from the menu dropdown. | ||||
| * This ensures we preserve the exact selected value even when multiple | ||||
| * options have the same itemText. | ||||
| */ | ||||
| private _menuSelectedValue: string = ''; | ||||
|
|
||||
| /** | ||||
| * An array of options to present in the Menu provided while typing into the input | ||||
| */ | ||||
|
|
@@ -302,13 +310,29 @@ export class Combobox extends Textfield { | |||
| const selected = (this.options || this.optionEls).find( | ||||
| (item) => item.value === target?.value | ||||
| ); | ||||
| this.itemValue = selected?.value || ''; | ||||
| this._menuSelectedValue = selected?.value || ''; | ||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need of creating a shadow copy here. Raise the guard so this.itemValue = selected?.value || '';
this._valueSetByMenu = true; |
||||
| this.value = selected?.itemText || ''; | ||||
| this.handleChange(); | ||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||||
| event.preventDefault(); | ||||
| this.open = false; | ||||
| this._returnItems(); | ||||
| this.focus(); | ||||
| } | ||||
|
|
||||
| protected override handleChange(): void { | ||||
| this.dispatchEvent( | ||||
| new CustomEvent('change', { | ||||
| bubbles: true, | ||||
| composed: true, | ||||
| detail: { | ||||
| value: this.itemValue, | ||||
| itemText: this.value, | ||||
| }, | ||||
| }) | ||||
| ); | ||||
| } | ||||
|
|
||||
|
Comment on lines
+323
to
+335
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The combobox documentation should be updated to reflect this.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Overriding
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Texfield is already dispatching
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I want to dispatch change event with detail like text and value, which is not added in Textfield. These details are needed for the user to handle UI on their side. Suppose, we have a requirement to store the itemText selected in the text box and pass it to API on a CTA click.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Can you please let me know where do we need to update the docs?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
@preethialuru Add a section just above the accessibility section, something like this:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @preethialuru Your use case is valid but I see ther eis big problem changing the event contract. It effects all change events not just menu selection. If you need @property({ type: String, attribute: 'selected-value' })
public selectedValue = '';Then you can capture it on your app side by combobox.addEventListener('change', () => {
const itemText = combobox.value; // display text
const value = combobox.selectedValue; // underlying ID
api.save({ id: value, name: itemText });
});This would be non-breaking and will work for all scenarios. |
||||
| public handleClosed(): void { | ||||
| this.open = false; | ||||
| this.overlayOpen = false; | ||||
|
|
@@ -339,10 +363,15 @@ export class Combobox extends Textfield { | |||
| } | ||||
| if (changed.has('value')) { | ||||
| this.filterAvailableOptions(); | ||||
| this.itemValue = | ||||
| this.availableOptions.find( | ||||
| (option) => option.itemText === this.value | ||||
| )?.value ?? ''; | ||||
| if (this._menuSelectedValue) { | ||||
| this.itemValue = this._menuSelectedValue; | ||||
| this._menuSelectedValue = ''; | ||||
| } else { | ||||
| const allOptions = this.options || this.optionEls; | ||||
| this.itemValue = | ||||
| allOptions.find((option) => option.itemText === this.value) | ||||
| ?.value ?? ''; | ||||
| } | ||||
|
Comment on lines
+366
to
+374
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Check the boolean guard instead of reading a shadow copy of the value if (this._valueSetByMenu) {
// itemValue was already set by handleMenuChange; just lower the guard.
this._valueSetByMenu = false;
} else {
// Value changed from typing or programmatic set -- resolve itemValue
// by looking up the matching option.
const allOptions = this.options || this.optionEls;
this.itemValue =
allOptions.find((option) => option.itemText === this.value)
?.value ?? '';
} |
||||
| } | ||||
| return super.shouldUpdate(changed); | ||||
| } | ||||
|
|
||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You dont' need this. You can replace this with a boolean. You are already setting
this.itemValuein line 313.. Instead add a flag here