Skip to content

Fix press gestures in portal windows#3666

Closed
iamEvanYT wants to merge 3 commits intomotiondivision:mainfrom
iamEvanYT:fix-portal-press-window
Closed

Fix press gestures in portal windows#3666
iamEvanYT wants to merge 3 commits intomotiondivision:mainfrom
iamEvanYT:fix-portal-press-window

Conversation

@iamEvanYT
Copy link

Summary

  • bind press lifecycle listeners to the pressed target's owning window instead of always using the global window
  • preserve press(window, ...) and press(document, ...) behavior while fixing elements rendered into another document/window
  • add regressions for iframe-owned press handling in motion-dom and portal-mounted whileTap/tap callbacks in framer-motion

Root Cause

press(...) in motion-dom attached pointerup and pointercancel listeners to the global window. When a press started on an element rendered into another document/window, the release lifecycle was tracked on the wrong browsing context, so whileTap, onTapStart, onTap, and onTapCancel could fail or immediately break.

Fix

Resolve the window from the press target:

  • Window targets use themselves
  • Document targets use defaultView
  • element targets use ownerDocument.defaultView

That resolved window is then used for the pointer lifecycle listeners.

Testing

  • add a motion-dom regression test for press lifecycle handling in both the current window and an owning iframe window
  • add a framer-motion regression test covering a portal-rendered motion.button with whileTap, onTapStart, onTap, and onTapCancel

Notes

  • I wasn't able to complete a clean local Jest run in this environment because of workspace/module-resolution setup issues, so the PR includes targeted regression coverage but has not been fully executed here

@greptile-apps
Copy link

greptile-apps bot commented Mar 26, 2026

Greptile Summary

This PR fixes press gestures for elements rendered into portal windows or iframes by resolving pointerup/pointercancel listeners against the target element's owning window instead of always using the global window. The change is well-scoped and includes targeted regression tests in both motion-dom and framer-motion.

  • Core fix (press/index.ts): getEventTargetWindow(target) is called at press-start time to determine the correct window, and all lifecycle listeners (pointerup, pointercancel) are bound to / removed from that resolved window.
  • New utility (get-event-target-window.ts): Cleanly handles the three target shapes — Window, Document, and Element — and resolves to the appropriate Window.
  • document success-check inconsistency: The (target as any) === targetWindow check correctly replaced === window for Window targets. However, the companion (target as any) === document check is still hardcoded to the global document. Calling press(iframeDocument, ...) would hit neither branch, silently falling through to isNodeOrChild, which would mark those presses as failures. Replacing document with targetWindow.document would make the two checks symmetric and consistent.
  • Fallback ?? window in getEventTargetWindow: The fallback to the global window for detached elements is reasonable for a browser-only gesture library, but would throw in SSR environments if the target has no ownerDocument.defaultView.

Confidence Score: 4/5

  • Safe to merge for the primary portal/iframe element use case; one targeted fix needed for the document-target success check before the fix is fully symmetric.
  • The core fix (binding lifecycle listeners to the target's owning window) is correct and well-tested for the stated use case. The (target as any) === document success check remaining hardcoded to the global document is a narrow inconsistency that only affects the niche press(iframeDocument, ...) call shape, not the portal-element scenario this PR targets. One line change (documenttargetWindow.document) would make the fix fully consistent.
  • packages/motion-dom/src/gestures/press/index.ts — the document success-check on line 94 should mirror the updated window check.

Important Files Changed

Filename Overview
packages/motion-dom/src/utils/get-event-target-window.ts New utility that resolves the owning Window from a given EventTarget (Window, Document, or Element); minor concern about ?? window fallback in SSR or detached-node contexts.
packages/motion-dom/src/gestures/press/index.ts Core press gesture logic updated to resolve pointerup/pointercancel listeners against the target's owning window; the document success-check is still hardcoded to the global document, which would break press(iframeDocument, ...) use cases.
packages/motion-dom/src/gestures/press/tests/index.test.ts New regression tests covering press lifecycle in both the current window and an iframe-owned button; tests are well structured and directly validate the fix.
packages/framer-motion/src/gestures/tests/press.test.tsx Added framer-motion regression test for a portal-rendered motion.button covering whileTap, onTapStart, onTap, and onTapCancel; also a minor formatting fix to an existing test.

Sequence Diagram

sequenceDiagram
    participant E as Element (in iframe)
    participant PH as press() handler
    participant GEW as getEventTargetWindow()
    participant IW as iframe.contentWindow
    participant GW as global window (before fix)

    Note over E,GW: Before fix
    E->>PH: pointerdown
    PH->>GW: addEventListener("pointerup", ...)
    E-->>GW: pointerup never arrives (wrong window)
    Note over PH: press lifecycle broken ❌

    Note over E,GW: After fix
    E->>PH: pointerdown
    PH->>GEW: getEventTargetWindow(element)
    GEW-->>PH: iframe.contentWindow
    PH->>IW: addEventListener("pointerup", ...)
    E->>IW: pointerup (bubbles up to iframe window)
    IW->>PH: onPointerUp fires
    Note over PH: press lifecycle completes ✅
Loading

Reviews (1): Last reviewed commit: "Fix press gestures in portal windows" | Re-trigger Greptile

@iamEvanYT iamEvanYT closed this Mar 26, 2026
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