You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Surfaced during PR #151 review (Claude reviewer thread PRRT_kwDOGE0Kh85_gT2R, #151 (comment)). Pre-existing — verified the same control-flow ordering is present on origin/master (class-component _handleShifting at src/ReactNativeZoomableView.tsx:685+). Filing as a follow-up; not in scope for #151.
Summary
When disablePanOnInitialZoom={true} is set and the user is mid-pan at initialZoom, programmatically calling ref.zoomTo() (auto-fit, timed/state-driven zoom) produces a single-frame pan jump as soon as the animation crosses out of initialZoom. The jump magnitude equals the cumulative finger displacement that occurred while pan was blocked.
Root cause
In _handleShifting (post-PR #151 worklet form), the panEnabled/disablePanOnInitialZoom early-return runs before_calcOffsetShiftSinceLastGestureState:
_calcOffsetShiftSinceLastGestureState is the only path that updates lastGestureCenterPosition.value during shift gestures. The seed-on-transition block in _handlePanResponderMove runs only on the gestureType !== 'shift' → 'shift' transition and does not re-fire mid-gesture. So while pan is blocked at initialZoom, the tracker stays frozen at the position recorded at the shift-transition.
publicZoomTo writes zoom.value/zoomToDestination.value/prevZoom.value but does not touch lastGestureCenterPosition (in contrast to the pinch-transition reset at the move-handler that resets both lastGestureCenterPosition and lastGestureTouchDistance).
When the animation lifts the early-return gate, the next move event computes delta = currentTouch - staleLastGestureCenterPosition and applies the entire blocked displacement in one frame.
The pre-PR-Reanimated #151 SPECS.md row that documented "Same jump occurs when disablePanOnInitialZoom auto-unblocks as zoom crosses above initialZoom" was removed in Reanimated #151's SPECS rewrite without fixing the underlying behavior; consider restoring documentation if not fixing immediately.
Surfaced during PR #151 review (Claude reviewer thread
PRRT_kwDOGE0Kh85_gT2R, #151 (comment)). Pre-existing — verified the same control-flow ordering is present onorigin/master(class-component_handleShiftingatsrc/ReactNativeZoomableView.tsx:685+). Filing as a follow-up; not in scope for #151.Summary
When
disablePanOnInitialZoom={true}is set and the user is mid-pan atinitialZoom, programmatically callingref.zoomTo()(auto-fit, timed/state-driven zoom) produces a single-frame pan jump as soon as the animation crosses out ofinitialZoom. The jump magnitude equals the cumulative finger displacement that occurred while pan was blocked.Root cause
In
_handleShifting(post-PR #151 worklet form), thepanEnabled/disablePanOnInitialZoomearly-return runs before_calcOffsetShiftSinceLastGestureState:_calcOffsetShiftSinceLastGestureStateis the only path that updateslastGestureCenterPosition.valueduring shift gestures. The seed-on-transition block in_handlePanResponderMoveruns only on thegestureType !== 'shift'→'shift'transition and does not re-fire mid-gesture. So while pan is blocked atinitialZoom, the tracker stays frozen at the position recorded at the shift-transition.publicZoomTowriteszoom.value/zoomToDestination.value/prevZoom.valuebut does not touchlastGestureCenterPosition(in contrast to the pinch-transition reset at the move-handler that resets bothlastGestureCenterPositionandlastGestureTouchDistance).When the animation lifts the early-return gate, the next move event computes
delta = currentTouch - staleLastGestureCenterPositionand applies the entire blocked displacement in one frame.Reproduction
<ReactNativeZoomableView disablePanOnInitialZoom initialZoom={1} ref={r} />._handleShiftingearly-returns each frame; tracker stays at the shift-transition seed.r.current.zoomTo(2)mid-drag.zoom !== 1, the next move event applies the cumulative blocked shift in one frame — visible discontinuity.Suggested fix
Either:
Option A — keep tracker current on blocked frames:
Option B —
publicZoomToresets gesture-tracking refs whengestureStarted.valueis true, mirroring the pinch-transition reset.Either change closes the asymmetry vs. the pinch-transition path that already exists in this file.
Notes
disablePanOnInitialZoom={true}(opt-in) + active 1-finger drag + programmaticzoomTo/zoomBy/withTimingmid-drag.