Skip to content

Fix dropdown closing on native picker interaction in Brave and Edge#1975

Merged
Flo0807 merged 3 commits into
developfrom
feature/fix-dropdown-close-on-native-picker
May 11, 2026
Merged

Fix dropdown closing on native picker interaction in Brave and Edge#1975
Flo0807 merged 3 commits into
developfrom
feature/fix-dropdown-close-on-native-picker

Conversation

@Flo0807
Copy link
Copy Markdown
Collaborator

@Flo0807 Flo0807 commented May 11, 2026

Summary

In older Chromium-based browsers (notably Brave and Microsoft Edge), interacting with a native form control inside a Backpex dropdown — most visibly the Category filter <select> — closes the dropdown mid-interaction. Chrome stable doesn't reproduce, but anyone on a lagging Chromium fork hits it.

Root cause

daisyUI's CSS-only dropdown stays open via :focus-within. Two Chromium quirks break that mechanism:

  1. Native picker steals focus from the document. When the user clicks a <select> inside the dropdown, the picker opens and document.activeElement becomes <body>. :focus-within flips false, daisyUI's CSS animates the dropdown closed.
  2. Synthesized "light-dismiss" click after picker dismissal. When the picker closes, Chromium fires a trusted click on the page underneath the picker — even though the user only interacted with the native widget. A naive outside-click handler treats this as a real outside click.

There's also a daisyUI-specific subtlety that bit the toggle behavior: while the dropdown is open, daisyUI applies pointer-events: none to the trigger, so pointer events at the trigger's position re-target to the dropdown root. A listener attached directly to the trigger wouldn't fire for the close-on-second-click.

Affects any daisyUI dropdown that contains a native picker (<select>, <input type=\"date\">, etc.) — not just nested ones.

Fix

A small LiveView hook (BackpexDropdown) that manages the open state via the dropdown-open class instead of relying on :focus-within:

  • Toggles on mousedown anywhere outside the menu (handles both the trigger and the re-targeted root).
  • Tracks where the most recent mousedown landed so the document click handler can distinguish real outside clicks from synthesized light-dismiss clicks (their mousedown is inside the dropdown).
  • Restores the open class in updated() so morphdom doesn't strip it during a LiveView re-render (e.g. after phx-change on a filter <select>).
  • Closes on Escape and returns focus to the trigger.

Wired via phx-hook=\"BackpexDropdown\" on the shared .dropdown component, so all dropdowns (filters, theme, user menu, column toggle, multi-select) benefit uniformly.

Test plan

  • Brave: open Filters → pick a Category → dropdown stays open and filter applies
  • Edge: same as above
  • Chrome: same as above (regression check — was already working)
  • Multi-select (Users) filter trigger toggles open/closed on repeated clicks
  • Clicking a checkbox inside the multi-select menu doesn't close the dropdown
  • Clicking the page outside the filter dropdown closes it
  • Pressing Escape while a dropdown is open closes it and returns focus to the trigger
  • Theme dropdown, user menu, column toggle still open/close correctly

daisyUI's CSS-only dropdown relies on `:focus-within` to stay open. That
mechanism breaks when the dropdown contains a native form control like a
`<select>`: in older Chromium-based browsers (notably Brave and Edge), opening
the native picker moves focus to `<body>`, so `:focus-within` flips false and
the dropdown collapses mid-interaction. Chromium also sometimes fires a
synthesized "light-dismiss" click on the page underneath after the picker
closes, which a naive outside-click handler would treat as a real outside
click.

Introduce a `BackpexDropdown` LiveView hook that manages the open state via
the `dropdown-open` class instead of relying on `:focus-within`. The hook:

  * Toggles on `mousedown` anywhere outside the menu — needed because daisyUI
    applies `pointer-events: none` to the trigger while the dropdown is open,
    which re-targets pointer events to the dropdown root.
  * Tracks where the most recent `mousedown` landed so the document `click`
    handler can distinguish real outside clicks from synthesized light-dismiss
    clicks (their `mousedown` is inside the dropdown).
  * Restores the open class in `updated()` so morphdom doesn't strip it on a
    LiveView re-render (e.g. after `phx-change` on a filter `<select>`).
  * Closes on Escape and returns focus to the trigger.

Wired up via `phx-hook="BackpexDropdown"` on the shared `.dropdown` component
so all Backpex dropdowns (filters, theme, user menu, column toggle,
multi-select) benefit uniformly.
@Flo0807 Flo0807 added the bug Something isn't working label May 11, 2026
When a user picks a value in a filter `<select>`, `phx-change` triggers a
LiveView patch and morphdom updates the surrounding form. Focus on the select
gets dropped during that update — even though the select node itself isn't
replaced.

Track the focused element inside the dropdown in `beforeUpdate()` and restore
focus in `updated()` if it ended up outside the dropdown afterwards. Also add
an `id` to the select filter form so morphdom has a stable identifier to
match against.
@Flo0807 Flo0807 enabled auto-merge May 11, 2026 15:38
@Flo0807 Flo0807 disabled auto-merge May 11, 2026 15:38
@Flo0807 Flo0807 requested a review from Copilot May 11, 2026 15:38
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses a Chromium (Brave/Edge) quirk where interacting with native form controls (e.g. <select>) inside a daisyUI dropdown causes the dropdown to close mid-interaction. It does so by moving dropdown “open” state management from CSS :focus-within to an explicit dropdown-open class controlled by a LiveView hook.

Changes:

  • Added a new LiveView hook (BackpexDropdown) that toggles/maintains dropdown-open, handles outside-click, Escape-to-close, and attempts to preserve focus across LiveView morphdom updates.
  • Wired the hook into the shared dropdown core component so all Backpex dropdowns benefit.
  • Added an explicit id to the select filter <select> (supports focus restoration) and updated hook exports + built JS artifacts.

Reviewed changes

Copilot reviewed 6 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
assets/js/hooks/_dropdown.js New hook implementing JS-managed dropdown open/close + focus restoration.
assets/js/hooks/index.js Exports the new BackpexDropdown hook.
lib/backpex/html/core_components.ex Attaches phx-hook="BackpexDropdown" to the shared dropdown component root.
lib/backpex/filters/select.ex Adds an explicit id to the rendered <select> to support focus restoration.
priv/static/js/backpex.esm.js Bundled ESM output updated to include the new hook.
priv/static/js/backpex.cjs.js Bundled CJS output updated to include the new hook.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread assets/js/hooks/_dropdown.js
Comment thread assets/js/hooks/_dropdown.js Outdated
Comment thread assets/js/hooks/_dropdown.js
  * Support keyboard activation: pressing Enter or Space on the trigger now
    toggles the dropdown via a `keydown` listener, matching WAI-ARIA button
    semantics.
  * Attach document-level listeners (`mousedown`, `click`, `keydown`) only
    while the dropdown is open. With multiple dropdowns on a page, closed
    instances no longer add to the event-handler tax.
  * Symmetric cleanup: `destroyed()` now removes the root `mousedown` and
    trigger `keydown` listeners alongside the document-level ones.

Also pre-set `mousedownInside = true` in `handleRootMousedown` because the
document-level mousedown listener is attached during `open()` and therefore
doesn't catch the mousedown that triggered the open itself.
@Flo0807 Flo0807 self-assigned this May 11, 2026
@Flo0807 Flo0807 added this pull request to the merge queue May 11, 2026
Merged via the queue into develop with commit 45e09d6 May 11, 2026
7 checks passed
@Flo0807 Flo0807 deleted the feature/fix-dropdown-close-on-native-picker branch May 11, 2026 18:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants