Skip to content

fix: Dialog - prevent open animation interruption by residual touch on Android - #4038

Merged
Yoavpagir merged 9 commits into
masterfrom
fix/dialog-android-open-spring-pan-interrupt
Sep 1, 2026
Merged

fix: Dialog - prevent open animation interruption by residual touch on Android#4038
Yoavpagir merged 9 commits into
masterfrom
fix/dialog-android-open-spring-pan-interrupt

Conversation

@Yoavpagir

Copy link
Copy Markdown
Collaborator

Description

On Android, a bottom-direction Dialog (and therefore ActionSheet, which builds on it) can open only part-way — resting below its final position with just the header visible — when it is opened as a direct result of a gesture-driven trigger (e.g. List.Item, whose TapGestureHandler fires onPress on gesture END while the touch is still settling). A plain touchable trigger (e.g. Button) lifts cleanly before the modal mounts and is unaffected.

Root cause: the Dialog's own panGesture has no activation threshold, so on Android the residual touch stream from the triggering gesture leaks into the freshly-mounted pan and drives visibility mid-open, interrupting the withSpring(1) open animation. Because the translation is interpolate(visibility, [0,1], [hiddenLocation, 0], CLAMP), an interrupted visibility (< 1) leaves the card translated part-way down. Dismiss is unaffected (the residual touch is long gone by then), which is why the close animation always plays from the full height.

Fix: add .minDistance(10) to the Dialog panGesture so it only engages on a deliberate drag. A residual, near-static touch can no longer activate the pan and overwrite visibility, while drag-to-dismiss (movement well beyond the threshold) keeps working unchanged.

Changelog

Dialog/ActionSheet: fixed the bottom sheet occasionally opening part-way on Android when triggered from a gesture-based component (e.g. List.Item); the open animation is no longer interrupted by the triggering touch.

Additional info

Under investigation for MOBAPP-2994 (Wix OneApp — "Mute Notifications" ActionSheet opening partially on Android). Draft while verifying on-device via the standard snapshot → wrapper → app build flow.

…n Android (minDistance)

A bottom Dialog/ActionSheet opened from a gesture-driven trigger (e.g. List.Item's
TapGestureHandler firing onPress on END) can rest part-way open on Android: the residual
touch leaks into the Dialog's own panGesture and drives `visibility` mid-open, interrupting
the open spring. Adding a minDistance activation threshold to the pan prevents a near-static
residual touch from engaging it, while drag-to-dismiss keeps working.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

✅ PR Description Validation Passed

All required sections are properly filled out:

  • Description
  • Changelog
  • Additional info

Your PR is good for review! 🚀


This validation ensures all sections from the PR template are properly filled.

@Yoavpagir
Yoavpagir marked this pull request as ready for review August 2, 2026 07:58
@yedidyak
yedidyak enabled auto-merge (squash) August 31, 2026 15:02
yedidyak
yedidyak previously approved these changes Aug 31, 2026
@yedidyak

Copy link
Copy Markdown
Collaborator

#forcemerge

Two Android-only failure modes share this remedy, both traced to RN 0.79's
ModalHostViewScreenSize() returning Size{0,0} on Android while iOS returns a
real RCTScreenSize (react/react-native#51048, fixed only in RN 0.81):
the dialog's open() call is gated on onLayout measuring a non-zero size, so
inside a 0x0 Modal it can either never fire (visibility stuck at 0) or fire
but have its spring orphaned mid-flight by the same underlying glitch
(visibility stuck partway).

The watchdog polls visibility every 400ms and re-opens only when the value
is unchanged since the last sample - never while it is decreasing, which is
what a close() in progress looks like before modalVisibility flips - and
gives up permanently after 8 attempts.

This treats the symptom, not the underlying RN bug, which stays open until
RN 0.81.

Verified in production CI: previously-deterministic Android failures now
pass on attempt 1 with no retries - sites-list-android-move-to-trash-confirm,
sites-list-android-action-sheet-opens, invites-admin-android-action-sheet-opens,
and the sites-list suite on both platforms.
Mounts the dialog already visible so open()/close() and the watchdog effect
close over the same render's shared value (react-native-reanimated's jest
mock, unlike the real implementation, allocates a fresh value per call
rather than persisting it across renders).

Covers: recovering a dialog stuck since mount and self-clearing once it
reaches full visibility; retrying while frozen and permanently stopping at
the attempt cap; never re-opening while visibility is decreasing (a close
in progress).
@yedidyak

yedidyak commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

#skipreview #automerge

1 similar comment
@yedidyak

yedidyak commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

#skipreview #automerge

auto-merge was automatically disabled September 1, 2026 11:17

Pull request was closed

@yedidyak yedidyak closed this Sep 1, 2026
@yedidyak yedidyak reopened this Sep 1, 2026
@Yoavpagir
Yoavpagir merged commit 2af5663 into master Sep 1, 2026
2 checks passed
@Yoavpagir
Yoavpagir deleted the fix/dialog-android-open-spring-pan-interrupt branch September 1, 2026 13:35
yedidyak added a commit that referenced this pull request Sep 1, 2026
…n Android (#4038)

* fix: Dialog - prevent open animation interruption by residual touch on Android (minDistance)

A bottom Dialog/ActionSheet opened from a gesture-driven trigger (e.g. List.Item's
TapGestureHandler firing onPress on END) can rest part-way open on Android: the residual
touch leaks into the Dialog's own panGesture and drives `visibility` mid-open, interrupting
the open spring. Adding a minDistance activation threshold to the pan prevents a near-static
residual touch from engaging it, while drag-to-dismiss keeps working.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* ci: trigger snapshot build

* fix: clarify minDistance rationale (MOBAPP-2994)

* test: add minDistance to Pan gesture jest mock (MOBAPP-2994)

* fix: Dialog - add open-animation watchdog for stranded Android opens

Two Android-only failure modes share this remedy, both traced to RN 0.79's
ModalHostViewScreenSize() returning Size{0,0} on Android while iOS returns a
real RCTScreenSize (react/react-native#51048, fixed only in RN 0.81):
the dialog's open() call is gated on onLayout measuring a non-zero size, so
inside a 0x0 Modal it can either never fire (visibility stuck at 0) or fire
but have its spring orphaned mid-flight by the same underlying glitch
(visibility stuck partway).

The watchdog polls visibility every 400ms and re-opens only when the value
is unchanged since the last sample - never while it is decreasing, which is
what a close() in progress looks like before modalVisibility flips - and
gives up permanently after 8 attempts.

This treats the symptom, not the underlying RN bug, which stays open until
RN 0.81.

Verified in production CI: previously-deterministic Android failures now
pass on attempt 1 with no retries - sites-list-android-move-to-trash-confirm,
sites-list-android-action-sheet-opens, invites-admin-android-action-sheet-opens,
and the sites-list suite on both platforms.

* test: cover Dialog open-animation watchdog

Mounts the dialog already visible so open()/close() and the watchdog effect
close over the same render's shared value (react-native-reanimated's jest
mock, unlike the real implementation, allocates a fresh value per call
rather than persisting it across renders).

Covers: recovering a dialog stuck since mount and self-clearing once it
reaches full visibility; retrying while frozen and permanently stopping at
the attempt cap; never re-opening while visibility is decreasing (a close
in progress).

* refactor: trim Dialog watchdog comments to the non-obvious rationale

* chore: retrigger CI

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Yedidya Kennard <yedidyak@wix.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants