Skip to content

Fix pre-filled value reset when clicking outside - #285

Open
Spone wants to merge 5 commits into
josefarias:mainfrom
Spone:patch-1
Open

Fix pre-filled value reset when clicking outside#285
Spone wants to merge 5 commits into
josefarias:mainfrom
Spone:patch-1

Conversation

@Spone

@Spone Spone commented Feb 21, 2026

Copy link
Copy Markdown

Problem

When an async combobox has a pre-filled value and the user opens it then clicks away without changing anything, the selection resets to the first alphabetical option instead of preserving the current value.

Kapture.2026-02-21.at.22.25.27.mp4

Root cause

For async comboboxes, opening triggers a fetch whose response arrives via Turbo Stream. At that point, _runCallback is called (not _preselectSingle), which calls _selectOnQuery with inputType = "hw:lockInSelection". This hits the branch:

} else if (inputType === "hw:lockInSelection" && this._ensurableOption) {
  this._select(this._ensurableOption, ...)

Since no option has aria-selected=true yet (_preselectSingle was bypassed), _selectedOptionElement is null, so _ensurableOption falls back to _visibleOptionElements[0] — the first alphabetical option — which gets incorrectly selected.

Fixes

Fix 1 — _hasSelection: adds missing return statements so the getter correctly reports whether a value is selected. Fixes the issue for sync comboboxes.

Fix 2 — _ensurableOption: adds _optionElementWithValue(this._fieldValue) as an intermediate fallback, so when no option is marked selected but the hidden field has a value, the matching option is found and preserved instead of falling back to the first visible one. Fixes the issue for async comboboxes.

// Before
get _ensurableOption() {
  return this._selectedOptionElement || this._visibleOptionElements[0]
}

// After
get _ensurableOption() {
  return this._selectedOptionElement || this._optionElementWithValue(this._fieldValue) || this._visibleOptionElements[0]
}

After the fix

Kapture.2026-02-21.at.22.28.07.mp4

@Spone Spone changed the title Add missing return to _hasSelection Fix pre-filled value reset whe click outside Feb 21, 2026
@Spone Spone changed the title Fix pre-filled value reset whe click outside Fix pre-filled value reset when clicking outside Feb 21, 2026
@josefarias
josefarias self-requested a review May 23, 2026 17:44

@josefarias josefarias left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Spone, I'm having a hard time reasoning through how this could happen organically so I used Claude to try and reproduce and it, too, had a hard time. Here's what it had to say (edited slightly by me, I think its reasoning is sound):

Thanks for tracking this down! A couple of things:

1. Is this still reproducible on the latest main? I couldn't trigger the prefilled reset organically, and I think the diagnosis may predate some code that's now on main. The root cause says _runCallback calls _selectOnQuery with hw:lockInSelection — but the controller now guards that (} else if (inputType !== "hw:lockInSelection")), so that branch is unreachable; the real lock-in goes through _lockInSelection_ensurableOption. Also, on a plain open our async combobox runs _preselectSingle (the lazy-frame load has no callback_id), so the option is marked and _ensurableOption never reaches the new fallback. Since _preselectSingle and the fix use the identical _optionElementWithValue(fieldValue) lookup, the fallback only matters if a re-render drops aria-selected after preselect (a Turbo stream re-rendering the listbox). Could you confirm it still reproduces on current main, and share the combobox config + what re-renders the listbox between open and click-away? A minimal repro would let us add a real regression test.

2. Regardless, this regresses include_blank. The new _optionElementWithValue(this._fieldValue) matches the blank option (data-value="") when the value is empty. Repro: blank combobox → type a non-matching query → click away → display resets to the blank label instead of staying empty. Passes on main, fails here. Guard:

get _ensurableOption() {
  return this._selectedOptionElement || this._optionElementWithFieldValue || this._visibleOptionElements[0]
}

get _optionElementWithFieldValue() {
  if (this._hasFieldValue) return this._optionElementWithValue(this._fieldValue)
}

Don't bother replying to everything it's asking. What I’d ask, if possible, is if you could provide a minimal reproduction repo (or a failing test case). Something I can run myself and see the behavior because I haven't been able to replicate just yet (reliably or not).

Separately, I do think the bug Claude points out is legit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants