Minimal reproducible example for issue #421 / PR #422.
On Android, an auto-grow EnrichedMarkdownTextInput (scrollEnabled={false}) inside a
ScrollView reports a height that is too tall on first render, leaving empty space below the
content. It self-corrects on the first keystroke. iOS is unaffected (it measures via TextKit on
the already-rendered text).
The defaultValue here is a message template whose placeholders are mention links, e.g.
[{firstName}](placeholder://contact.first_name). The label is short on screen but the
placeholder://... URL is hidden in the markdown source. InputMeasurementStore.initialMeasure
measures the raw markdown (counting those invisible URLs), over-estimating the height into extra
lines, until the next text change forces a re-measure.
- Expo SDK 56,
expo-router, React Native 0.85.3, New Architecture enabled react-native-enriched-markdown0.6.0 (unpatched)- Android only (iOS is the control: correct height from the start)
npm install
# Android: where the bug reproduces
npx expo run:android
# iOS: control, no bug
npx expo run:iosThis is a native module, so a dev build is required (expo run:*). Expo Go will not work.
- On Home, tap Open editor.
- Scroll to the bottom of the message.
- Bug: there is extra empty space below the last line (the input is taller than its rendered text, so the scroll content extends past the text).
- Tap the text and type a space (or any character).
- The input recalculates and snaps to the correct (shorter) height; the extra space disappears.
On iOS the height is correct from the start and does not change on the first keystroke.
- The bug only shows inside a
ScrollView: there the child is measured withUNDEFINEDheight (its raw auto-grow height), so the over-estimate is visible. Outside aScrollViewanAT_MOSTclamp hides it. - The
defaultValueis a long message so it overflows the viewport and runs under the keyboard; scroll to the bottom to see the extra space past the last line. The more/longer the hidden URLs, the larger the over-estimate.
InputMeasurementStore.initialMeasure measures the raw markdown source:
val text = props?.getString("defaultValue") ?: props?.getString("placeholder") ?: "I"PR #422 parses the markdown into its rendered plain text first, using the library's existing parser
(the same one setValueFromJS uses), so the first measure matches what is actually rendered:
val markdown = props?.getString("defaultValue") ?: props?.getString("placeholder") ?: "I"
val text = InputParser.parseToPlainTextAndRanges(markdown).plainTextSame family as #281 / #366 (incorrect Android measurement, iOS correct), but on the input component those fixes didn't cover.