Skip to content

fix: listbox and dropdown with enter transitions - #28

Open
RobertJoonas wants to merge 1 commit into
mainfrom
listbox-and-dropdown-fix-for-transitions
Open

RobertJoonas wants to merge 1 commit into
mainfrom
listbox-and-dropdown-fix-for-transitions

Conversation

@RobertJoonas

@RobertJoonas RobertJoonas commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Fixes a bug where the "opening" click on a dropdown/listbox trigger button is already considered an "outside" click from the options wrapper's perspective, which ends up closing the options before it even renders.

The fix is to not consider trigger button clicks as outside clicks.

Since phx-click-away needs to be attached to a single element, and attaching it to a wrapper element would mean introducing a dead-zone (due to possibly unequal widths between the trigger and options) the option that we're left with is to do it with a document click handler in the hook.

The bug doesn't surface until an animation is used, which makes the LiveView JS internals behave differently:

Scenario A — transition configured:

  1. Click hits the trigger button. Our handleToggle() handler runs first, since its listener is on the button element.
  2. It calls showListboxAndFocus(), which tells LiveView "show the panel" via execJS(...).
  3. LiveView immediately calls el.dispatchEvent(new Event("phx:show-start")) — synchronously, right there, still inside step 2's call stack.
  4. That synchronously runs our handleShowStart(), which marks the panel as now open.
  5. handleToggle() finishes. The click event keeps bubbling upward.
  6. It reaches document. LiveView's click-away check now runs — still part of the same original click, just later in the bubble. It looks at the panel: is it open? Yes (step 4 already flipped that, moments ago). Was the click "outside" it? Also yes (button is a sibling of the panel, not inside it). Verdict: close it.
  7. Net result, all within one click: opened, then closed.
  8. Visible behaviour: the panel doesn't appear at all.

Scenario B — no transition:

1–2. Same as above.
3. LiveView doesn't call dispatchEvent yet — it schedules it via requestAnimationFrame for the next frame.
4. handleToggle() finishes. Nothing has told our hook the panel is "open" yet.
5. Click keeps bubbling, reaches document, LiveView's click-away check runs. It looks at the panel: still marked closed (step 3 hasn't fired yet). Verdict: nothing to close. No-op, like it's always been.
6. The click event is now fully done and out of the picture. Only after that — on the next animation frame — does LiveView finally call dispatchEvent(phx:show-start), and the panel opens for real, safely, with no click-away check left to race against.

So the difference emerges from step 3, where behaviour is determined by existence of transition in/out classes (source code ref)

When using transitions for dropdown/listbox options becoming visible,
the opening click is already considered an "outside" click from the
options wrapper's perspective, which ends up closing the options before
it even renders.

The fix is to not consider trigger button clicks as outside clicks.

Since `phx-click-away` needs to be attached to a single element, and
attaching it to a wrapper element would mean introducing a dead-zone
(due to possibly unequal widths between the trigger and options) the
option that we're left with is to do it with a document click handler
in the hook.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.

1 participant