Native module:
- Add `fullHeight` prop to BottomSheetView on Android and iOS
- iOS: fullHeight sets detent to .large, skips content observation
- Android: fullHeight uses isFitToContents=false with expandedOffset=statusBarHeight
Android bottom sheet behavior overhaul:
- Switch normal sheets to isFitToContents=false with expandedOffset, enabling
proper 3-state snapping (half-expanded ↔ expanded ↔ hidden). Previously
isFitToContents=true prevented settling at half-expanded during gestures,
making it impossible to swipe back down from expanded.
- preventExpansion sheets keep isFitToContents=true with maxHeight cap, plus
a state callback safety net that bounces EXPANDED→HALF_EXPANDED. This
catches an edge case where rapid content resizes during opening can invert
the expanded/half-expanded offsets, causing the sheet to dismiss.
- Add requestLayout() after maxHeight changes in updateLayout() so the
FrameLayout actually re-measures (fixes keyboard padding not resizing sheet)
- Set backgroundTint=transparent in the theme to remove Material3 surface
tint that was visible in gaps between sheet and screen edge
Keyboard handling:
- Remove native keyboard expansion logic (insets listener, isKeyboardVisible
tracking, manual STATE_EXPANDED on keyboard show)
- Replace with JS-side keyboard padding: ScrollableInner listens for RN
Keyboard events and adds bottom padding equal to keyboard height
- Generalize useOnKeyboardDidShow → useOnKeyboard(eventName, cb)
Callers:
- Replace minHeight: screenHeight hack with fullHeight: true in EditProfile,
ChangeHandle, AddAppPassword, ImageAltText, GifAltText, LanguageSelect,
FollowDialog, GifSelect, StarterPack, Select, NewChat, ShareViaChat,
DraftsList, WizardEditList, ListAddRemoveUsers, CreateOrEditList
- Remove unused useWindowDimensions imports
- ServerInput: simplify to just preventExpansion (was platform-specific)
- ImageAltTextDialog: remove old {height: 300} keyboard spacer hack
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- getContentHeight: use max of children heights instead of DFS index 1
(old approach measured the drag handle, not the content)
- observe height via OnLayoutChangeListener on each direct child instead
of OnGlobalLayoutListener (RN bypasses requestLayout, so global listener
never fires for RN-driven layout changes)
- defer state changes during DRAGGING/SETTLING to avoid interrupting
dismiss swipes, apply when gesture settles
- subtract insets.top from NativeView height on android (matching iOS)
to prevent scroll content clipping at bottom of full-height sheets
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OnLayoutChangeListener only fires on future layout changes, but
dialog.show() triggers the initial content layout synchronously
before the listener is registered. The sheet was stuck at the tiny
halfExpandedRatio computed from the pre-layout content height (0).
After registering the listener, do an immediate updateLayout() if
the content view already has a nonzero height, so we pick up the
real dimensions.
https://claude.ai/code/session_01Rp7ef1h3fKh6fhStcjqLJ5
The lambda had 10 destructured parameters but OnLayoutChangeListener
only passes 9 (view, left, top, right, bottom, oldLeft, oldTop,
oldRight, oldBottom). Remove the extra placeholder.
https://claude.ai/code/session_01Rp7ef1h3fKh6fhStcjqLJ5
Now that both platforms observe content height natively (iOS via KVO,
Android via OnLayoutChangeListener), the JS-callable updateLayout()
is unused. Remove:
- updateLayout() and prevLayoutDetentIdentifier from iOS SheetView
- AsyncFunction("updateLayout") from both platform module definitions
- updateLayout method from the JS BottomSheetNativeComponent
Android's updateLayout() is kept as a private implementation detail
since the keyboard insets listener and native layout observer both
call it internally.
https://claude.ai/code/session_01Rp7ef1h3fKh6fhStcjqLJ5
The KVO callback was calling updateLayout(), which has two problems for
content-driven updates:
1. The prevLayoutDetentIdentifier guard can block updates. When
updateDetents triggers animateChanges, the delegate callback
sheetPresentationControllerDidChangeSelectedDetentIdentifier fires
and updates selectedDetentIdentifier to the system-assigned custom
detent identifier. On the next KVO callback, prevLayoutDetentIdentifier
(.medium) no longer matches selectedDetentIdentifier, so the update
is silently dropped.
2. Re-reading frame.size.height from the view hierarchy instead of using
the observed bounds can return a stale value during rapid layout passes.
Fix: have the KVO callback call updateDetents directly with the observed
height, bypassing both the guard and the frame re-read. The guard in
updateLayout() is preserved for the manual/fallback code path.
https://claude.ai/code/session_01Rp7ef1h3fKh6fhStcjqLJ5
Same approach as the iOS KVO change: use View.OnLayoutChangeListener on
the content view to detect height changes purely on the native side.
This eliminates the setTimeout → updateLayout() JS bridge hack that was
a known workaround for async timing issues on Android.
The onLayout callback in JS is now only used for the iOS 15 fallback
height measurement. All other height observation is native.
https://claude.ai/code/session_01Rp7ef1h3fKh6fhStcjqLJ5
Replace the async JS bridge round-trip (onLayout → updateLayout()) with
native KVO observation on the content view's bounds. When React Native's
Yoga layout engine sets the frame, the KVO callback fires synchronously
on the same run loop iteration and calls updateLayout() directly. This
eliminates the timing issues that surfaced after enabling iOS 26 design.
https://claude.ai/code/session_01Rp7ef1h3fKh6fhStcjqLJ5
When preventExpansion is true, the maxHeight cap prevented the sheet
from expanding when the keyboard opened. Lift the cap temporarily when
the keyboard appears (updateLayout restores it on dismiss). Also
broadened the state check to handle SETTLING state during rapid
keyboard open/close.
Fixes#9943
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>