Skip to content

fix(rtl): horizontal initialScrollIndex near the end settles on the mirror index#478

Open
Mohamed-kassim wants to merge 2 commits into
LegendApp:mainfrom
Mohamed-kassim:fix/horizontal-rtl-initial-scroll-mirror
Open

fix(rtl): horizontal initialScrollIndex near the end settles on the mirror index#478
Mohamed-kassim wants to merge 2 commits into
LegendApp:mainfrom
Mohamed-kassim:fix/horizontal-rtl-initial-scroll-mirror

Conversation

@Mohamed-kassim

Copy link
Copy Markdown

Fixes #476.

Problem

A horizontal RTL LegendList opened with an initialScrollIndex near the end can settle on the RTL mirror index instead of the requested one (e.g. request item 595 of 604 → settles on item 10 = total − 1 − 594).

Legend List keeps logical (LTR) scroll offsets and converts to the native RTL coordinate via toNativeHorizontalOffset. Two spots on the initial-scroll path skipped that conversion:

  1. scrollToFallbackOffset (the initial-scroll watchdog/retry in checkFinishedScroll.ts) dispatched the unconverted logical offset directly to the native scrollTo. When the watchdog wins the race against the bootstrap scroll's native onScroll round-trip (more likely on a large jump / slow device / with recycleItems), the list lands on the mirror.
  2. The initialContentOffset seed (the first native contentOffset) was also taken unconverted for RTL.

Fix

Convert the offset in scrollToFallbackOffset (mirroring the normal doScrollTo dispatch), and skip the unconverted RTL initialContentOffset seed so the converting initial-scroll dispatch positions the list.

Verification

  • bun run tsc:src ✅, bunx biome check ✅, bun test ✅ (no new failures; one pre-existing old-arch bootstrap test fails on main unchanged).
  • Reproduced + confirmed fixed in a minimal Expo repro (@legendapp/list@3.0.6, RN 0.85.3, Android): without the fix the list settles on the mirror index (deterministic, 5/5 cold starts); with the fix it settles on the requested item. Repro: https://github.com/Mohamed-kassim/legend-list-rtl-repro

Scoped to horizontal RTL — non-RTL and vertical paths are unaffected (isHorizontalRTLProps / horizontal guards).

… RTL initialContentOffset seed

A horizontal RTL list opened with initialScrollIndex near the end can settle on
the RTL mirror index. The initial-scroll watchdog/retry (scrollToFallbackOffset)
dispatched the *unconverted* logical offset, and the initialContentOffset seed
was also taken unconverted for RTL. Convert both, mirroring the normal doScrollTo
dispatch path.

Fixes LegendApp#476

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 405cb2c8d1

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/components/LegendList.tsx Outdated
Comment on lines 527 to 528
isHorizontalRTLProps({ horizontal, rtl })
? undefined

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Don't drop offset-only initial scrolls in RTL

When a horizontal RTL list uses initialScrollOffset without initialScrollIndex, this branch returns undefined even though the initial-scroll session is offset-only. initializeInitialScrollOnMount then treats that as 0, and because alwaysDispatchInitialScroll is false when an offset prop is present, it finishes and clears the initial scroll at the origin instead of dispatching the converted offset; the initialScrollOffset prop is effectively ignored for this case. Limit this skip to bootstrap sessions or force a converted dispatch for offset-only sessions.

Useful? React with 👍 / 👎.

@Mohamed-kassim Mohamed-kassim Jun 21, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in bcc3863.

Addresses Codex review: the previous skip applied to all horizontal RTL lists, so
an offset-only RTL initial scroll (initialScrollOffset without initialScrollIndex)
dropped its offset and finished at 0. Scope the RTL skip to bootstrap sessions
(initialScrollIndex / initialScrollAtEnd), where the converting bootstrap dispatch
re-applies the position; offset-only sessions keep their resolved offset.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

Horizontal RTL: initialScrollIndex near the end settles on the mirror index

1 participant