Skip to content

fix(dom-query): close presence-mounted overlays on Escape before the next frame - #3543

Merged
segunadebayo merged 5 commits into
mainfrom
fix/dismissable-presence-escape
Oct 25, 2026
Merged

segunadebayo merged 5 commits into
mainfrom
fix/dismissable-presence-escape

Conversation

@segunadebayo

@segunadebayo segunadebayo commented Oct 25, 2026 •

Copy link
Copy Markdown
Member

Closes #3483

Problem

whenNode checked for the node in a microtask, then fell back to the next frame. Content mounted through presence (lazyMount / unmountOnExit) commits after that microtask, so the dismissable layer, and its Escape listener, only registered a frame later. An Escape in that window was lost and the menu stayed open.

Fix

While waiting for the frame, whenNode also listens for DOM insertions on the caller's root node through a shared MutationObserver (one native observer per target and config) and resolves on whichever comes first.

  • whenNode, trackDismissableElement and trackDismissableBranch take a getRootNode option; without it, only the frame fallback runs.
  • Every machine with a deferred dismissable layer or branch passes scope.getRootNode, so shadow roots and iframes are observed where the content is queried.

Tests

  • menu/presence example in React, Vue, Solid and Svelte.
  • e2e/menu-presence.e2e.ts: with frames paused, open then press Escape. React fails on main (aria-expanded stays true), passes with the fix.
  • Held-frame probe in the browser: on main, React and Solid lose Escape (Vue and Svelte were unaffected); with the fix, all four close and reopen.
  • dom-query and dismissable unit tests pass; React e2e for menu, menu-nested, popover, dialog, select: 84 passed, 1 flaky (select Space key, flaky on main too).

@vercel

vercel Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
zag-nextjs Ready Ready Preview Oct 25, 2026 7:01pm UTC
zag-solid Ready Ready Preview Oct 25, 2026 7:01pm UTC
zag-svelte Ready Ready Preview Oct 25, 2026 7:01pm UTC
zag-vue Ready Ready Preview Oct 25, 2026 7:01pm UTC
zag-website-v2 Building Building Preview Oct 25, 2026 7:01pm UTC
1 Skipped Deployment
Project Deployment Actions Updated
zag-website Ignored Ignored Preview Oct 25, 2026 7:01pm UTC

Request Review

@changeset-bot

changeset-bot Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 40c0351

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 85 packages
Name Type
@zag-js/dom-query Patch
@zag-js/dismissable Patch
@zag-js/cascade-select Patch
@zag-js/color-picker Patch
@zag-js/combobox Patch
@zag-js/date-picker Patch
@zag-js/dialog Patch
@zag-js/drawer Patch
@zag-js/hover-card Patch
@zag-js/menu Patch
@zag-js/navigation-menu Patch
@zag-js/popover Patch
@zag-js/select Patch
@zag-js/toast Patch
@zag-js/tour Patch
@zag-js/core Patch
@zag-js/accordion Patch
@zag-js/angle-slider Patch
@zag-js/avatar Patch
@zag-js/carousel Patch
@zag-js/checkbox Patch
@zag-js/clipboard Patch
@zag-js/collapsible Patch
@zag-js/date-input Patch
@zag-js/editable Patch
@zag-js/file-upload Patch
@zag-js/floating-panel Patch
@zag-js/image-cropper Patch
@zag-js/listbox Patch
@zag-js/marquee Patch
@zag-js/number-input Patch
@zag-js/pagination Patch
@zag-js/password-input Patch
@zag-js/pin-input Patch
@zag-js/presence Patch
@zag-js/progress Patch
@zag-js/qr-code Patch
@zag-js/radio-group Patch
@zag-js/rating-group Patch
@zag-js/scroll-area Patch
@zag-js/signature-pad Patch
@zag-js/slider Patch
@zag-js/splitter Patch
@zag-js/steps Patch
@zag-js/switch Patch
@zag-js/tabs Patch
@zag-js/tags-input Patch
@zag-js/timer Patch
@zag-js/toc Patch
@zag-js/toggle-group Patch
@zag-js/toggle Patch
@zag-js/tooltip Patch
@zag-js/tree-view Patch
@zag-js/aria-hidden Patch
@zag-js/auto-resize Patch
@zag-js/focus-trap Patch
@zag-js/focus-visible Patch
@zag-js/hotkeys Patch
@zag-js/i18n-utils Patch
@zag-js/interact-outside Patch
@zag-js/popper Patch
@zag-js/remove-scroll Patch
@zag-js/scroll-snap Patch
@zag-js/preact Patch
@zag-js/react Patch
@zag-js/solid Patch
@zag-js/svelte Patch
@zag-js/vanilla Patch
@zag-js/vue Patch
@zag-js/async-list Patch
@zag-js/file-utils Patch
@zag-js/anatomy-icons Patch
@zag-js/anatomy Patch
@zag-js/docs Patch
@zag-js/store Patch
@zag-js/types Patch
@zag-js/collection Patch
@zag-js/color-utils Patch
@zag-js/utils Patch
@zag-js/date-utils Patch
@zag-js/highlight-word Patch
@zag-js/json-tree-utils Patch
@zag-js/live-region Patch
@zag-js/rect-utils Patch
@zag-js/stringify-state Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

This branch was successfully deployed

4 active deployments
Preview – zag-solid — 40c03518 Deployed Oct 25, 2026 by vercel[bot]
Preview – zag-vue — 40c03518 Deployed Oct 25, 2026 by vercel[bot]
Preview – zag-svelte — 40c03518 Deployed Oct 25, 2026 by vercel[bot]
Preview – zag-nextjs — 40c03518 Deployed Oct 25, 2026 by vercel[bot]
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.

dismissable: Escape is lost for a frame when menu content mounts through presence (lazyMount)

1 participant