feat: add getCaretRect() to read the caret bounding rect#652
Open
dennistan95 wants to merge 2 commits into
Open
feat: add getCaretRect() to read the caret bounding rect#652dennistan95 wants to merge 2 commits into
dennistan95 wants to merge 2 commits into
Conversation
Adds an async getCaretRect() to the EnrichedTextInput imperative handle so consumers can anchor UI (link popovers, floating toolbars, mention menus) to the live caret. onChangeSelection only exposes character offsets, so there was no way to position an overlay at the caret; native already has the geometry (UITextView caretRectForPosition: on iOS, Layout on Android) — this surfaces it to JS, mirroring the existing getHTML()/requestHTML request-response round-trip. - spec: OnCaretRectEvent event, onCaretRect prop, requestCaretRect command - JS: getCaretRect() (requestId->promise with a timeout fallback so it resolves null instead of hanging), onCaretRect handler, unmount cleanup - iOS: caretRectForPosition: converted into the component view's space; emits a `valid` flag when the rect is null/infinite - Android: Layout primary-horizontal + line top/bottom, padding/scroll-adjusted, px->dp for parity with iOS points; new OnCaretRectEvent + manager registration Note: this package ships pre-generated Fabric codegen (includesGeneratedCode) and a compiled lib/, so the generated artifacts + lib/ need regenerating (codegen + bob build) before release. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR adds a new imperative API, getCaretRect(), to EnrichedTextInput so JS consumers can retrieve the caret’s on-screen bounding rect (in editor view coordinates) and position anchored UI (toolbars/popovers/menus) relative to the live insertion point.
Changes:
- Adds a new caret-rect request/response round-trip:
requestCaretRectcommand +onCaretRectevent (withrequestIdandvalidflag). - Implements
getCaretRect()in the native JS wrapper with a timeout-basednullfallback to avoid hanging promises. - Adds native implementations for iOS (
caretRectForPosition) and Android (Layout-based caret geometry) and dispatches the result via a direct event.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/types.ts | Introduces CaretRect and adds getCaretRect() to the imperative handle type. |
| src/spec/EnrichedTextInputNativeComponent.ts | Adds OnCaretRectEvent, onCaretRect prop, and requestCaretRect command to the codegen spec. |
| src/native/EnrichedTextInput.tsx | Implements the JS-side requestId→promise mapping, timeout fallback, event handler, and unmount cleanup. |
| ios/EnrichedTextInputView.mm | Adds the native command handler and emits caret rect geometry via onCaretRect. Also modifies selection clamping logic. |
| android/src/main/java/com/swmansion/enriched/textinput/events/OnCaretRectEvent.kt | Adds a new direct event payload for caret rect responses. |
| android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputViewManager.kt | Registers the new event and wires the requestCaretRect command to the view. |
| android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt | Computes caret rect from Layout, converts px→dp, and dispatches OnCaretRectEvent. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+1341
to
+1345
| - (void)setCustomSelection:(NSInteger)visibleStart end:(NSInteger)visibleEnd { | ||
| NSString *text = textView.textStorage.string; | ||
|
|
||
| NSUInteger actualStart = [self getActualIndex:visibleStart text:text]; | ||
| NSUInteger actualEnd = [self getActualIndex:visibleEnd text:text]; | ||
| NSUInteger textLength = textView.textStorage.string.length; | ||
| NSInteger clampedStart = MAX(visibleStart, 0); | ||
| NSInteger clampedEnd = MAX(visibleEnd, clampedStart); | ||
| NSUInteger actualStart = MIN((NSUInteger)clampedStart, textLength); |
| var widthDp = 0.0 | ||
| var heightDp = 0.0 | ||
| val currentLayout = layout | ||
| val pos = selectionStart |
Comment on lines
+460
to
+466
| /** | ||
| * Resolves the current caret's bounding rect (for positioning UI like a link | ||
| * popover or floating toolbar) in the editor view's coordinate space. Resolves | ||
| * `null` if unavailable or the native event doesn't arrive — callers should | ||
| * treat `null` as "no anchor". | ||
| */ | ||
| getCaretRect: () => Promise<CaretRect | null>; |
… target - iOS: revert an unrelated setCustomSelection change that slipped in from a packaging diff (restores getActualIndex ZWSP-aware mapping). - Android: anchor requestCaretRect to selectionEnd (the active caret), matching iOS selectedTextRange.end, so a non-empty selection reports the live caret. - Web: implement getCaretRect via ProseMirror coordsAtPos (selection end), converted to editor-element coordinates — so the cross-platform instance type is fully satisfied and web/build targets don't break. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author
|
Thanks for the review — all three addressed in the latest commit:
|
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.
Summary
Adds an async
getCaretRect()to theEnrichedTextInputimperative handle so consumers can anchor UI — link popovers, floating toolbars, mention menus — to the live caret.onChangeSelectionexposes only character offsets, so there's currently no way to position an overlay at the caret. Native already has the pixel geometry (UITextView caretRectForPosition:on iOS,Layouton Android); this just surfaces it to JS, mirroring the existinggetHTML()/requestHTMLrequest→response round-trip exactly.API
Resolves the caret rect in the editor view's coordinate space (RN dp / iOS points), or
nullwhen unavailable. A built-in timeout resolvesnullrather than leaking a pending promise, so callers always get a definite result.Changes
src/spec/EnrichedTextInputNativeComponent.tsOnCaretRectEventevent,onCaretRectprop,requestCaretRectcommand (+supportedCommands)src/types.tsCaretRecttype +getCaretRect()onEnrichedTextInputInstancesrc/native/EnrichedTextInput.tsxgetCaretRect()(requestId→promise with timeout fallback),onCaretRecthandler, unmount cleanupios/EnrichedTextInputView.mmrequestCaretRect:→caretRectForPosition:converted into the component view's space; emits avalidflag when the rect is null/infiniteandroid/.../EnrichedTextInputView.ktrequestCaretRect()→Layoutprimary-horizontal + line top/bottom, padding/scroll-adjusted, px→dp for parity with iOS pointsandroid/.../EnrichedTextInputViewManager.ktrequestCaretRectoverride + event registrationandroid/.../events/OnCaretRectEvent.ktrequestId,x,y,width,height,valid)Both platforms emit a
validflag so JS can distinguish "no caret / unknown" from a real zero-origin rect.Note for maintainers
This package ships pre-generated Fabric codegen (
includesGeneratedCode: true) and a compiledlib/. After review, the generated artifacts (ios/generated,android/generated) andlib/will need regenerating (codegen +bobbuild) before a release picks up the new command/event.Testing
valid = falsewith no selection.getCaretRect()resolvesnullvia the timeout (no hang).