Skip to content

Commit aa9fe11

Browse files
committed
Fix Combobox click-outside behavior for non-primitive values
Fixes #188 - Combobox now correctly preserves selected values when clicking outside after selecting a non-primitive option. Root cause: In handleOptionClick, setSelectedOption was called before onChange, so this.args.value hadn't been updated yet. Changes: - Swap order in -option.js: call onChange first, then setSelectedOption - Set inputValue in setSelectedOption so input immediately reflects selection - Add test from PR #189 for non-primitive values with displayValue
1 parent 55e7812 commit aa9fe11

3 files changed

Lines changed: 51 additions & 2 deletions

File tree

ember-headlessui/addon/components/combobox.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,8 @@ export default class ComboboxComponent extends Component {
435435
this.inputElement?.focus();
436436
}
437437

438-
this._originalValue = this.inputValue;
438+
this._originalValue = this.args.value;
439+
this.inputValue = this.args.value;
439440
}
440441

441442
@action

ember-headlessui/addon/components/combobox/-option.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ export default class ComboboxOptionComponent extends Component {
3434

3535
if (this.args.disabled) return;
3636

37-
this.args.setSelectedOption(this, e);
3837
this.callOnChangeWithSelectedValue();
38+
this.args.setSelectedOption(this, e);
3939
}
4040

4141
@action

test-app/tests/integration/components/combobox-test.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,54 @@ module('Integration | Component | <Combobox>', function (hooks) {
237237
assert.dom(getComboboxInput()).hasValue('B');
238238
});
239239

240+
test('selecting an option puts the display value into Combobox.Input when displayValue is provided and values are objects', async function (assert) {
241+
this.set('onChange', (value) => {
242+
this.set('value', value);
243+
});
244+
245+
this.setProperties({
246+
value: null,
247+
a: { value: 'a' },
248+
b: { value: 'b' },
249+
c: { value: 'c' },
250+
displayValue: (option) => {
251+
return option?.value?.toUpperCase() || 'None';
252+
},
253+
});
254+
255+
await render(hbs`
256+
<Combobox
257+
@value={{this.value}}
258+
@onChange={{this.onChange}}
259+
as |combobox|
260+
>
261+
<combobox.Input @displayValue={{this.displayValue}}/>
262+
<combobox.Button data-test="headlessui-combobox-button-2">Trigger</combobox.Button>
263+
<combobox.Options as |options|>
264+
<options.Option @value={{this.a}}>Option A</options.Option>
265+
<options.Option @value={{this.b}}>Option B</options.Option>
266+
<options.Option @value={{this.c}}>Option C</options.Option>
267+
</combobox.Options>
268+
</Combobox>
269+
`);
270+
271+
await click(getComboboxButton());
272+
assertComboboxList({ state: ComboboxState.Visible });
273+
274+
await click(getComboboxOptions()[1]);
275+
assert.dom(getComboboxInput()).hasValue('B');
276+
277+
await click(getComboboxButton());
278+
assertComboboxList({ state: ComboboxState.Visible });
279+
280+
assert.dom(getComboboxInput()).hasValue('B');
281+
282+
await click(document.body);
283+
assertComboboxList({ state: ComboboxState.InvisibleUnmounted });
284+
285+
assert.dom(getComboboxInput()).hasValue('B');
286+
});
287+
240288
test('opening and closing the combobox should not trigger spurious onChange events on the input', async function (assert) {
241289
let inputOnChangeCallCount = 0;
242290
let inputOnChangeValues = [];

0 commit comments

Comments
 (0)