feat: scaleY pinch-to-zoom with counter-scaling and zoomScale persistence#20
Draft
anton-patrushev wants to merge 7 commits into
Draft
Conversation
Adds a `children` prop to CalendarBodyProps, rendered inside BodyContext.Provider after the calendar grid. This allows mounting components that need BodyContext access (e.g. SharedValue capture, zoom persistence) without depending on NowIndicatorComponent's lifecycle — which only mounts when today is visible. Also fixes positionUtils CalendarData import. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
During pinch gesture, only updates `zoomScale` SharedValue and applies
`transform: [{ scaleY }]` to the content container — O(1) per frame
instead of O(N) per-event height recalculations.
On gesture end, commits `timeIntervalHeight *= zoomScale; zoomScale = 1`
for a one-time O(N) recalc at the final zoom level.
Consumers receive `zoomScale` via SizeAnimation and BodyContext to
optionally counter-scale text or make discrete layout decisions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Counter-scale drag dots and default text in DraggableEvent and DraggingEvent (View → Animated.View with counterScaleStyle) - Counter-scale horizontal grid lines in HorizontalLine (stays 1px regardless of zoom) - Clamp zoomScale to [min, max] before computing zoomPercent in onZoomChange reaction — prevents spring overshoot during rubber-band snap-back from reporting out-of-range values Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On Android, RNGH's transformPoint computes focalY using a cached scroll offset that doesn't update after setNativeProps. The per-frame anchor calculation accumulated this error, causing the viewport to drift during pinch-to-zoom. Fix: capture focalY, offsetY, and zoomScale once at gesture start and derive the scroll offset purely from the zoom ratio change. This makes the anchor computation independent of per-frame focalY accuracy. Also fix scrollTo fallback to use animated:false (instant) instead of animated:true which caused visible lag. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two issues caused the viewport to drift on Android during pinch:
1. setNativeProps({ contentOffset }) doesn't reliably scroll on Android
when the ref targets RNGH's ScrollView wrapper. Replaced with
scrollTo() from Reanimated which calls the native scrollTo method
directly on the underlying scrollable view.
2. The ScrollView's native pan gesture fires simultaneously with the
pinch (via simultaneousHandlers). As fingers spread/squeeze, their
midpoint naturally moves — Android interprets this as a pan, scrolling
the view and fighting our programmatic offset. Fix: disable
scrollEnabled on the native ScrollView during pinch (onBegin) and
re-enable on onFinalize. scrollTo() still works with scrollEnabled
disabled since it only blocks touch-initiated scrolls.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
kairos2109
approved these changes
Mar 13, 2026
Root cause: transformOrigin: '0% 0%' on the inner scale container was ignored on Android when split across static + animated style objects. scaleY defaulted to center-origin, pushing content both up and down instead of only down — causing visible drift despite correct scroll math. Fix: replace transformOrigin with an explicit translateY that simulates top-left origin: translateY = (height/2) * (zoomScale - 1). This works identically on both platforms. Also: - Add isPinching SharedValue + onScroll guard on Android as safety net - Use scrollTo instead of setNativeProps for scroll position updates - Use gesture-start snapshot for focal-point anchoring - Remove all debug logging Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…write The scaleY pinch-to-zoom rewrite accidentally dropped: - dayEndLineStyle prop (dashed border between days in multi-provider scroll) - hapticService.selection() on day change (both CalendarContainer and useSyncedList) - isDayEnd/isDayStart forwarding through _renderResourceItem -> BodyResourceItem -> ResourceBoard -> VerticalLine Restores full parity with patch 018 (day-end-line-and-haptic) from the GlossGenius core-mobile patch chain.
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
transform: [{ scaleY: zoomScale }]—timeIntervalHeightstays constant, onlyzoomScalechanges per frameonZoomChangeto clampzoomScalebefore computingzoomPercent, preventing spring overshoot from reporting out-of-range valuesinitialZoomScaleprop for consumer-provided initial zoom,minZoomScale/maxZoomScaleon contextcounterScaleStylevia BodyContext for shared counter-scale animated styleArchitecture
Performance model
zoomScale) + 1 outer spacer height update. GPU handles visual scaling. No content re-layout.timeIntervalHeightchanges →minuteHeightderives →timelineHeightderives → ALL event heights derive → full layout recalcStacked on
fix/remove-header-bottom-borderTest plan
goToHour(),goToDate(),zoom()work at all zoom levels🤖 Generated with Claude Code