feat(virtual-core): iOS momentum-safe scroll adjustments via CSS offset#1189
feat(virtual-core): iOS momentum-safe scroll adjustments via CSS offset#1189piecyk wants to merge 1 commit into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
✅ Files skipped from review due to trivial changes (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughDefers visual scroll adjustments on iOS WebKit into a CSS margin offset during touch/momentum, folds the deferred delta into offset reads, flushes the compensation when safe or before programmatic scrolls, refines backward-scroll resize adjustment based on measurement cache, and updates tests, docs, example CSS, and a changeset. ChangesiOS Momentum Scroll Safety and Adjustment Refinement
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
packages/virtual-core/tests/index.test.tsParsing error: "parserOptions.project" has been provided for Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
View your CI Pipeline Execution ↗ for commit dfe7dc9
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗ ☁️ Nx Cloud last updated this comment at |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/virtual-core/src/index.ts (1)
742-756: 💤 Low valueConsider edge case:
scrollOffsetis null when force-flushing.In
_forceFlushIosDeviation, ifscrollOffsetisnulland_iosDeferredAdjustmentis non-zero,rawBrowserScrollwould compute as a negative value (0 - adjustment). While this scenario is unlikely in practice (deviation is only accumulated after scroll events setscrollOffset), a defensive guard would prevent unexpected behavior if the invariant is violated.🛡️ Optional defensive guard
private _forceFlushIosDeviation = () => { if (this._iosDeferredAdjustment === 0) return + if (this.scrollOffset === null) { + // No scroll offset yet; just clear the deviation state + this._iosDeferredAdjustment = 0 + this._applyIosDeviation() + return + } const rawBrowserScroll = (this.scrollOffset ?? 0) - this._iosDeferredAdjustment🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/virtual-core/src/index.ts` around lines 742 - 756, If this._iosDeferredAdjustment is non-zero but this.scrollOffset is null, computing rawBrowserScroll yields an incorrect value; add a defensive guard at the top of _forceFlushIosDeviation to handle null scrollOffset (e.g. if this.scrollOffset == null then clear _iosDeferredAdjustment and call _applyIosDeviation and return) so you don't call _scrollToOffset with an invalid rawBrowserScroll; reference the method name _forceFlushIosDeviation and the fields scrollOffset, _iosDeferredAdjustment and the helpers _applyIosDeviation and _scrollToOffset when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/virtual-core/src/index.ts`:
- Around line 742-756: If this._iosDeferredAdjustment is non-zero but
this.scrollOffset is null, computing rawBrowserScroll yields an incorrect value;
add a defensive guard at the top of _forceFlushIosDeviation to handle null
scrollOffset (e.g. if this.scrollOffset == null then clear
_iosDeferredAdjustment and call _applyIosDeviation and return) so you don't call
_scrollToOffset with an invalid rawBrowserScroll; reference the method name
_forceFlushIosDeviation and the fields scrollOffset, _iosDeferredAdjustment and
the helpers _applyIosDeviation and _scrollToOffset when making the change.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e304110e-eb8c-4e87-8cfc-e899b4dc56ce
📒 Files selected for processing (5)
.changeset/ios-momentum-scroll.mddocs/chat.mdexamples/react/chat/src/index.csspackages/virtual-core/src/index.tspackages/virtual-core/tests/index.test.ts
d9919d9 to
dfe7dc9
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/chat.md`:
- Line 58: Update the rationale to explain that useCallback is applied to
getItemKey to stabilize its function reference so memoized values that include
getItemKey (such as getMeasurements which depends on
this.getMeasurementOptions()) don't treat measurement options as changed on
every render; specifically mention wrapping getItemKey in useCallback to keep a
stable reference, how passing a new getItemKey identity each render can
invalidate getMeasurements/getMeasurementOptions memoization and cause
unnecessary measurement rebuilds/cache invalidation, and note this keeps the
virtualizer from triggering extra recomputation while clarifying that edge-key
change detection itself is based on returned key values.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 77b40f76-f2ad-4786-be9a-e5b0ed7c5cee
📒 Files selected for processing (5)
.changeset/ios-momentum-scroll.mddocs/chat.mdexamples/react/chat/src/index.csspackages/virtual-core/src/index.tspackages/virtual-core/tests/index.test.ts
✅ Files skipped from review due to trivial changes (2)
- examples/react/chat/src/index.css
- .changeset/ios-momentum-scroll.md
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/virtual-core/src/index.ts
- packages/virtual-core/tests/index.test.ts
On iOS WebKit, writing scrollTop during momentum scroll cancels the in-flight scroll. Instead of writing scrollTop, apply a negative marginTop on the container element to visually compensate for above-viewport size changes. The CSS offset is flushed to a real scrollTop write once momentum fully settles. Changes: - Add CSS offset (marginTop) approach for iOS scroll adjustments - Defer adjustments through touch→momentum→settled lifecycle - Force-flush CSS offset before programmatic scroll operations - Compensate scrollOffset for CSS offset in range calculations - Guard against Safari elastic overscroll during flush - Clean up CSS offset on unmount - Refine backward-scroll suppression: first measurements always adjust (needed for prepend), re-measurements skip during backward scroll (avoids scrollTop cascade jank) - Add overflow-anchor: none to chat example CSS - Update chat docs with iOS section and getItemKey best practices
dfe7dc9 to
1e6c48d
Compare
On iOS WebKit, writing
scrollTopduring momentum scroll cancels the in-flight scroll. Instead of writingscrollTop, we now apply a negativemarginTopon the container element to visually compensate for above-viewport size changes. The CSS offset is flushed to a realscrollTopwrite once momentum fully settles.marginTop/marginLeft)scrollToIndex,scrollToOffset,scrollBy)scrollOffsetfor active CSS offset in range calculationsscrollTopcascade jank🎯 Changes
✅ Checklist
pnpm run test:pr.🚀 Release Impact
Summary by CodeRabbit
Improvements
Behavior
Documentation
Tests