Skip to content

fix: centre without a viewport and keep the selected value - #22

Merged
scrollDynasty merged 3 commits into
mainfrom
fix/layout-driven-centering-and-value-preservation
Jul 27, 2026
Merged

fix: centre without a viewport and keep the selected value#22
scrollDynasty merged 3 commits into
mainfrom
fix/layout-driven-centering-and-value-preservation

Conversation

@scrollDynasty

@scrollDynasty scrollDynasty commented Jul 27, 2026

Copy link
Copy Markdown
Owner

React Native never runs an Android layout pass for native children: ReactViewGroup.requestLayout() and ReactViewGroup.onLayout() are both empty, so scrollToPositionWithOffset only parked an anchor nobody consumed. Combined with centering scheduled through nested post() calls off a height that was still 0, a picker mounted in a collapsed container stayed on index 0 with only its centre row drawn. Centering is now layout-driven and drives the RecyclerView's measure/layout itself.

selectedIndex was also clamped against the items list it was replacing. Fabric hands props over as an unordered map, so a range change permanently moved the selection and emitted onChange for a value the user never picked. The raw requested index is kept and re-resolved against the new list, prop application order is fixed, and change events are suppressed while props are applied.

Inside a Modal the same layout fix applies — DialogRootViewGroup only reports its size asynchronously — and gestures are handed over via NativeGestureUtil, since that root discards requestDisallowInterceptTouchEvent.

iOS gets the same re-resolution plus a layoutSubviews reassert.

Android instrumented tests and the iOS test additions were not executed; see CHANGELOG for what was and was not verified.

BREAKING CHANGE: DateDrumPickerValue now requires day, month and year; use DateDrumPickerPartialValue where an incomplete date is intentional.

Summary

What does this PR change and why?

Type of change

  • Bug fix
  • New feature
  • Documentation
  • Refactor / chore
  • Native change (Android Kotlin / iOS Swift)
  • Tests only

Checklist

  • I read CONTRIBUTING.md
  • I tested on Android and/or iOS (platforms I changed)
  • I ran yarn lint, yarn build, yarn typecheck, and yarn test from the repo root
  • I added or updated tests in the correct folder (see CONTRIBUTING.md — tests)
  • I updated docs if the public API changed
  • I committed yarn.lock if package.json dependencies changed
  • I did not add unnecessary dependencies
  • I removed debug logs from library code
  • I did not hardcode demo values in src/, android/, or ios/ (example app only)
  • For native changes: I considered memory leaks and scroll performance

CI

PRs to main run the full CI workflow automatically. New tests in standard locations are picked up without editing ci.yml.

Screenshots / recordings (UI changes)

If applicable, add before/after visuals.

Related issues

Fixes # (issue number)

Summary by CodeRabbit

  • New Features

    • Added support for partial date values when configuring the date picker.
    • Updated the 0.3.0 release with clearer controlled-value behavior.
  • Bug Fixes

    • Improved selection persistence when lists, date ranges, or layouts change.
    • Fixed centering and rendering in collapsed containers and modals.
    • Prevented onChange from firing during programmatic updates.
    • Improved dependent-column updates and Android gesture handling.
  • Documentation

    • Added troubleshooting guidance for mounting, sizing, selection, and modal issues.
    • Documented warnings for invalid or empty selections.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@scrollDynasty, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 45 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 49929a2b-07e1-4347-b377-245d39457dae

📥 Commits

Reviewing files that changed from the base of the PR and between 08097e8 and b856aba.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • README.md
  • android/src/androidTest/java/com/drumpicker/DrumPickerRegressionTest.kt
  • android/src/main/java/com/drumpicker/DrumPickerView.kt
  • example/src/App.tsx
  • src/DrumPicker.native.tsx
📝 Walkthrough

Walkthrough

Changes

Date picker selection stability

Layer / File(s) Summary
Date value contracts and controlled date mapping
src/dateDrumPickerLogic.ts, src/DateDrumPicker.tsx, src/index.tsx
Adds DateDrumPickerPartialValue, updates clamping and exports, documents controlled values, and clamps the year column index.
Controlled mounting and layout diagnostics
src/DrumPicker.native.tsx, src/__tests__/*
Adds development diagnostics for invalid indices and zero-height layouts, with controlled mounting, updates, range, ref, and remount tests.
iOS selection re-resolution
ios/DrumPickerView.swift, ios/DrumPickerTests/*
Preserves requested selection across layout and item-list changes and adds corresponding regression tests.
Android centering and gesture coordination
android/src/main/java/com/drumpicker/*, android/src/androidTest/*
Adds layout-aware centering, ordered prop application, programmatic event suppression, native gesture notifications, and Android regression coverage.
Release documentation and example alignment
package.json, CHANGELOG.md, README.md, example/src/App.tsx
Publishes version 0.3.0, documents the updated behavior, and initializes the example booking date from the current date.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ReactNative
  participant DateDrumPicker
  participant DrumPickerNative
  participant NativeWheel
  ReactNative->>DateDrumPicker: provide controlled day, month, year
  DateDrumPicker->>DrumPickerNative: pass resolved column items and selectedIndex
  DrumPickerNative->>NativeWheel: apply selection after layout
  NativeWheel-->>DrumPickerNative: report user-driven selection
  DrumPickerNative-->>DateDrumPicker: emit onChange for user interaction
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main fixes: centering without a viewport and preserving the selected value across changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/layout-driven-centering-and-value-preservation

Warning

Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

React Native never runs an Android layout pass for native children:
ReactViewGroup.requestLayout() and ReactViewGroup.onLayout() are both empty,
so scrollToPositionWithOffset only parked an anchor nobody consumed. Combined
with centering scheduled through nested post() calls off a height that was
still 0, a picker mounted in a collapsed container stayed on index 0 with only
its centre row drawn. Centering is now layout-driven and drives the
RecyclerView's measure/layout itself.

selectedIndex was also clamped against the items list it was replacing. Fabric
hands props over as an unordered map, so a range change permanently moved the
selection and emitted onChange for a value the user never picked. The raw
requested index is kept and re-resolved against the new list, prop application
order is fixed, and change events are suppressed while props are applied.

Inside a Modal the same layout fix applies — DialogRootViewGroup only reports
its size asynchronously — and gestures are handed over via NativeGestureUtil,
since that root discards requestDisallowInterceptTouchEvent.

iOS gets the same re-resolution plus a layoutSubviews reassert.

Android instrumented tests and the iOS test additions were not executed; see
CHANGELOG for what was and was not verified.

BREAKING CHANGE: DateDrumPickerValue now requires day, month and year; use
DateDrumPickerPartialValue where an incomplete date is intentional.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🧹 Nitpick comments (4)
ios/DrumPickerTests/DrumPickerWheelViewTests.swift (1)

150-201: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover user-driven and circular re-resolution paths.

These cases only use setSelectedIndex; add list-swap regressions after pickerView(_:didSelectRow:inComponent:) and circular recentering. Also run the iOS test target before merge—the PR notes it was not executed.

🤖 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 `@ios/DrumPickerTests/DrumPickerWheelViewTests.swift` around lines 150 - 201,
Extend the DrumPicker wheel tests to cover selection preservation when items are
replaced after pickerView(_:didSelectRow:inComponent:) updates the selection,
and when circular recentering re-resolves the selected value. Add regressions
for both list-swap paths alongside the existing setSelectedIndex tests, then run
the iOS test target and confirm it passes.
src/DrumPicker.native.tsx (1)

244-269: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Zero-height warning can fire for the collapsed-mount case this PR explicitly supports.

A picker mounted inside a closed accordion/collapsed container for longer than 1.5s is a documented, supported scenario (DateDrumPicker value JSDoc, src/DateDrumPicker.tsx lines 63-67), yet it will log this warning until the container expands. Consider skipping the warning while height is 0 and the view has never received a layout event at all, or downgrading the wording to "not measured yet".

🤖 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 `@src/DrumPicker.native.tsx` around lines 244 - 269, Update the zero-height
warning logic around hasMeasuredRef and handleLayout so a picker that has never
received any layout event, including one mounted in a collapsed container, does
not emit the “will never be visible” warning. Track whether layout has occurred
separately from whether its height is positive, and only warn for a measured
view that remains zero-height; preserve the existing cleanup and
development-only behavior.
android/src/androidTest/java/com/drumpicker/DrumPickerRegressionTest.kt (1)

53-116: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

These tests bypass DrumPickerViewManager.updateProperties, so the new prop ordering is untested.

Each test calls setItemsProp/setSelectedIndexProp directly in a hand-chosen order, which validates DrumPickerView but not the ORDERED_PROPS two-phase application that this PR added to guard against Fabric's unordered map. A small unit test that feeds a ReactStylesDiffMap with selectedIndex and a shrinking items and asserts the resulting selection would close that gap.

🤖 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 `@android/src/androidTest/java/com/drumpicker/DrumPickerRegressionTest.kt`
around lines 53 - 116, The regression tests currently call DrumPickerView
setters directly and do not exercise DrumPickerViewManager.updateProperties or
its ORDERED_PROPS two-phase ordering. Add a focused manager/unit test that
applies a ReactStylesDiffMap containing selectedIndex and a shrinking items
collection, then assert the final selection is valid and matches the ordered
application behavior. Keep the existing view-level tests unchanged.
src/__tests__/DateDrumPickerControlled.test.tsx (1)

19-29: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

readColumns derives month from the raw index while day/year read labels.

month: monthPicker!.props.selectedIndex + 1 silently assumes the month column always starts at January. It holds for these fixtures (minDate month is 1), but any future case where minDate/maxDate narrows the month range in the selected year will make this read wrong rather than fail loudly. Reading the label keeps all three columns consistent.

🤖 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 `@src/__tests__/DateDrumPickerControlled.test.tsx` around lines 19 - 29, Update
readColumns so the month value is derived from the selected monthPicker item via
the existing label helper, matching the day and year columns. Remove the raw
selectedIndex + 1 assumption while preserving the returned one-based month
value.
🤖 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 `@android/src/main/java/com/drumpicker/DrumPickerView.kt`:
- Around line 657-693: Update the animated path in centerOnSelectedIndex so
suppressChangeEvent cannot remain latched when smoothScrollToPosition does not
produce a SCROLL_STATE_IDLE transition. After determining the current centered
index, clear the flag when the target is already centered or otherwise ensure
SCROLL_STATE_DRAGGING resets it before user interaction; preserve suppression
for genuine programmatic animations until completion.

In `@example/src/App.tsx`:
- Around line 77-84: Update the booking date initialization around bookingDate
and bookingMinMax to derive both values from one stable today snapshot captured
during initial setup. Reuse that shared Date value for the day, month, year, and
booking bounds so the controlled value and minDate remain consistent across
midnight.

---

Nitpick comments:
In `@android/src/androidTest/java/com/drumpicker/DrumPickerRegressionTest.kt`:
- Around line 53-116: The regression tests currently call DrumPickerView setters
directly and do not exercise DrumPickerViewManager.updateProperties or its
ORDERED_PROPS two-phase ordering. Add a focused manager/unit test that applies a
ReactStylesDiffMap containing selectedIndex and a shrinking items collection,
then assert the final selection is valid and matches the ordered application
behavior. Keep the existing view-level tests unchanged.

In `@ios/DrumPickerTests/DrumPickerWheelViewTests.swift`:
- Around line 150-201: Extend the DrumPicker wheel tests to cover selection
preservation when items are replaced after
pickerView(_:didSelectRow:inComponent:) updates the selection, and when circular
recentering re-resolves the selected value. Add regressions for both list-swap
paths alongside the existing setSelectedIndex tests, then run the iOS test
target and confirm it passes.

In `@src/__tests__/DateDrumPickerControlled.test.tsx`:
- Around line 19-29: Update readColumns so the month value is derived from the
selected monthPicker item via the existing label helper, matching the day and
year columns. Remove the raw selectedIndex + 1 assumption while preserving the
returned one-based month value.

In `@src/DrumPicker.native.tsx`:
- Around line 244-269: Update the zero-height warning logic around
hasMeasuredRef and handleLayout so a picker that has never received any layout
event, including one mounted in a collapsed container, does not emit the “will
never be visible” warning. Track whether layout has occurred separately from
whether its height is positive, and only warn for a measured view that remains
zero-height; preserve the existing cleanup and development-only behavior.
🪄 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 Plus

Run ID: fc369ba0-dc7c-4437-8bb3-539cf0e7d9d5

📥 Commits

Reviewing files that changed from the base of the PR and between fdeedd1 and 08097e8.

⛔ Files ignored due to path filters (2)
  • src/__tests__/snapshots/__snapshots__/DateDrumPicker.snapshot.test.tsx.snap is excluded by !**/*.snap
  • src/__tests__/snapshots/__snapshots__/DrumPicker.snapshot.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (16)
  • CHANGELOG.md
  • README.md
  • android/src/androidTest/AndroidManifest.xml
  • android/src/androidTest/java/com/drumpicker/CollapsedTestActivity.kt
  • android/src/androidTest/java/com/drumpicker/DrumPickerRegressionTest.kt
  • android/src/main/java/com/drumpicker/DrumPickerView.kt
  • android/src/main/java/com/drumpicker/DrumPickerViewManager.kt
  • example/src/App.tsx
  • ios/DrumPickerTests/DrumPickerWheelViewTests.swift
  • ios/DrumPickerView.swift
  • package.json
  • src/DateDrumPicker.tsx
  • src/DrumPicker.native.tsx
  • src/__tests__/DateDrumPickerControlled.test.tsx
  • src/dateDrumPickerLogic.ts
  • src/index.tsx

Comment thread android/src/main/java/com/drumpicker/DrumPickerView.kt
Comment thread example/src/App.tsx Outdated
@scrollDynasty
scrollDynasty merged commit a1bcaa7 into main Jul 27, 2026
14 checks passed
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.

1 participant