feat: on link press for input#547
Open
eszlamczyk wants to merge 6 commits into
Open
Conversation
There was a problem hiding this comment.
Pull request overview
Adds an onLinkPress callback to EnrichedMarkdownTextInput so links are pressable when the input is not focused (notes-app style), while preserving native editing behavior when focused. This introduces a small cross-platform touch/hit-testing layer to detect link taps and (when applicable) consume them without focusing the input.
Changes:
- JS/TS: add
onLinkPressAPI + internalisOnLinkPressSetgating prop for Fabric. - iOS/macOS: track touch-down in
hitTestand vetotextViewShouldBeginEditing:(macOS viamouseDown:) to emitonLinkPresswithout focusing. - Android: add shared
charOffsetAtutility and consume link taps onACTION_UP(sendACTION_CANCELtosuper) to prevent focus/keyboard.
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/react-native-enriched-markdown/src/index.tsx | Re-exports OnLinkPressEvent from the input API. |
| packages/react-native-enriched-markdown/src/EnrichedMarkdownTextInputNativeComponent.ts | Adds codegen event type + isOnLinkPressSet native prop + onLinkPress event. |
| packages/react-native-enriched-markdown/src/EnrichedMarkdownTextInput.tsx | Adds onLinkPress prop wiring and isOnLinkPressSet flag propagation. |
| packages/react-native-enriched-markdown/ios/utils/ENRMTextHitTest.m | Adds strict hit-testing to avoid false positives past line bounds. |
| packages/react-native-enriched-markdown/ios/utils/ENRMTextHitTest.h | Exposes ENRMCharacterIndexAtPointStrict declaration. |
| packages/react-native-enriched-markdown/ios/input/ENRMInputTextView.mm | Tracks touch-down in hitTest for unfocused link press resolution. |
| packages/react-native-enriched-markdown/ios/input/EnrichedMarkdownTextInput+Internal.h | Adds internal hooks for touch/mouse link handling. |
| packages/react-native-enriched-markdown/ios/input/EnrichedMarkdownTextInput.mm | Implements unfocused link press detection and event emission. |
| packages/react-native-enriched-markdown/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextTapUtils.kt | New shared charOffsetAt helper with bounds checks. |
| packages/react-native-enriched-markdown/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkLongPressMovementMethod.kt | Uses shared charOffsetAt instead of local duplicate logic. |
| packages/react-native-enriched-markdown/android/src/main/java/com/swmansion/enriched/markdown/input/layout/InputEventEmitter.kt | Adds emitter method to dispatch onLinkPress. |
| packages/react-native-enriched-markdown/android/src/main/java/com/swmansion/enriched/markdown/input/events/OnLinkPressEvent.kt | New RN event class for link presses. |
| packages/react-native-enriched-markdown/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputView.kt | Implements unfocused link-tap candidate tracking and consumption. |
| packages/react-native-enriched-markdown/android/src/main/java/com/swmansion/enriched/markdown/input/EnrichedMarkdownTextInputManager.kt | Exports onLinkPress event name and isOnLinkPressSet prop. |
| docs/INPUT.md | Documents unfocused link pressing behavior and usage example. |
| docs/API_REFERENCE.md | Adds onLinkPress API reference entry. |
| apps/react-native-example/src/screens/playground/PlaygroundScreen.tsx | Adds example onLinkPress usage (Alert). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
eszlamczyk
marked this pull request as ready for review
July 17, 2026 10:26
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What/Why?
Closes #381
Adds an
onLinkPresscallback toEnrichedMarkdownTextInput, making links pressable while the input is not focused - the notes-app model: reading opens links, editing edits them.Providing the callback gates the whole behavior - no extra prop needed:
onLinkPress({ url })and consumes the tap: the input does not focus and the keyboard does not open. Tapping anywhere else focuses as usual.Fires for manual links, mentions, and auto-detected links (store + transient detector ranges).
Always-pressable (Apple Notes-style) was considered and rejected: while editing, a tap means "place the cursor here", and hijacking some taps to open URLs mid-edit fights the platform text-editing gestures. Unfocused-only gives each mode one unambiguous tap meaning.
Implementation notes:
isOnLinkPressSetprop (same pattern asisOnChangeMarkdownSet): under Fabric, event handlers never reach native as props, so native needs the flag to know it should consume taps rather than just emit.UITextInteraction's internal recognizers can't be reliably beaten with gesture failure requirements, and they delay raw touch delivery (touchesBegannever fires for a focusing tap). Instead the touch-down is tracked inhitTest:withEvent:(the one synchronous step UIKit can't skip) and the press is resolved by vetoingtextViewShouldBeginEditing:- guaranteed to keep the keyboard closed. Long-press/dragguards (0.5 s timeout) keep selection gestures working. macOS: handled in
mouseDown:before first-responder assignment.onTouchEvent(slop + long-press timeout); on UP over a link, super getsACTION_CANCELinstead of the UP, so pressed state and pending focus/click callbacks are cleared. Hit-testing reuses the bounds-checked offset logic from fix(android): prevent false link hit detection for taps outside text bounds #523, extracted into a sharedcharOffsetAt(TextTapUtils.kt).ENRMCharacterIndexAtPointStrict— a bounds-checked hit test mirroring the Android fix(android): prevent false link hit detection for taps outside text bounds #523 fix, so taps past a line's end don't false-positive on the nearest link.Testing
In the example app Playground, set content with a link (e.g. via Set Raw Markdown:
[link text](https://reactnative.dev)), keep the input unfocused and tap the link → alert with the URL, input stays unfocused. Tap plain text → input focuses. While focused, tap the link -> caret placement, no event. Blur and tap the link again -> event fires.Also added a new story to the storybook
Screenshots
PR Checklist