diff --git a/packages/react-native-nitro-input/CHANGELOG.md b/packages/react-native-nitro-input/CHANGELOG.md index 3069c49..72a363d 100644 --- a/packages/react-native-nitro-input/CHANGELOG.md +++ b/packages/react-native-nitro-input/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 0.3.6 + +- **A controlled `selection` rendered before the latest keystrokes is + dropped**, as `value` already was and as React Native's `TextInput` does. + Typed faster than JS renders (a slow Android phone, a busy JS thread), a + caret computed for an earlier text landed in a field that already had more + in it, and the next characters went in behind the first ones: a code field + driven by `selection` took "123456" as "156234". Uncontrolled fields are + unchanged. + ## 0.3.5 - **`focusedValue`**: pass a shared value (Reanimated's `useSharedValue(false)`) diff --git a/packages/react-native-nitro-input/android/src/main/java/com/margelo/nitro/nitroinput/HybridNitroInputView.kt b/packages/react-native-nitro-input/android/src/main/java/com/margelo/nitro/nitroinput/HybridNitroInputView.kt index 47903d6..1e88546 100644 --- a/packages/react-native-nitro-input/android/src/main/java/com/margelo/nitro/nitroinput/HybridNitroInputView.kt +++ b/packages/react-native-nitro-input/android/src/main/java/com/margelo/nitro/nitroinput/HybridNitroInputView.kt @@ -532,12 +532,18 @@ class HybridNitroInputView(private val context: ThemedReactContext) : HybridNitr } /// A controlled `selection`; -1 on either end means "leave the caret alone". + /// Like `text`, dropped when it was rendered before JS saw the latest native + /// edits (React Native's `TextInput` does the same): typed faster than JS + /// renders, a caret computed for "1" landed in a field already showing + /// "1234", and the next digits went in behind the first one. An uncontrolled + /// field sends a count of 0 and is applied as before. private fun flushSelectionIfNeeded() { if (!selectionDirty) return selectionDirty = false val start = clampInt(selectionStart, -1, Int.MAX_VALUE, -1) val end = clampInt(selectionEnd, -1, Int.MAX_VALUE, -1) if (start < 0 || end < 0) return + if (mostRecentEventCount > 0 && mostRecentEventCount < eventCount) return inputView.setSelection(start, end) } diff --git a/packages/react-native-nitro-input/ios/HybridNitroInputView.swift b/packages/react-native-nitro-input/ios/HybridNitroInputView.swift index 7886a5c..96fa869 100644 --- a/packages/react-native-nitro-input/ios/HybridNitroInputView.swift +++ b/packages/react-native-nitro-input/ios/HybridNitroInputView.swift @@ -434,13 +434,21 @@ final class HybridNitroInputView: HybridNitroInputViewSpec, RecyclableView { } /// A controlled `selection`: `-1` on either end means "leave the caret alone", - /// which is what an uncontrolled field sends on every render. + /// which is what an uncontrolled field sends on every render. Like `text`, + /// dropped when it was rendered before JS saw the latest native edits (React + /// Native's `TextInput` does the same): typed faster than JS renders, a caret + /// computed for "1" landed in a field already showing "1234". An + /// uncontrolled field sends a count of 0 and is applied as before. private func applySelectionIfNeeded() { guard selectionDirty else { return } selectionDirty = false let start = Self.clampInt(selectionStart, -1, Int(Int32.max), fallback: -1) let end = Self.clampInt(selectionEnd, -1, Int(Int32.max), fallback: -1) guard start >= 0, end >= 0 else { return } + snapshotLock.lock() + let nativeCount = snapshot.eventCount + snapshotLock.unlock() + if mostRecentEventCount.isFinite, mostRecentEventCount > 0, Int(mostRecentEventCount) < nativeCount { return } inputView.setSelection(start: start, end: end) }