From b90a66696d7b79687c50987cd07293945a7c3bfc Mon Sep 17 00:00:00 2001 From: Islam Rustamov Date: Sun, 26 Jul 2026 17:23:37 +0300 Subject: [PATCH 1/4] feat: add deletion by ref --- .../enriched/textinput/EnrichedTextInputView.kt | 13 +++++++++++++ .../textinput/EnrichedTextInputViewManager.kt | 4 ++++ ios/EnrichedTextInputView.mm | 14 ++++++++++++++ src/native/EnrichedTextInput.tsx | 3 +++ src/spec/EnrichedTextInputNativeComponent.ts | 2 ++ src/types.ts | 2 ++ src/web/EnrichedTextInput.tsx | 11 +++++++++++ 7 files changed, 49 insertions(+) diff --git a/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt b/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt index 023cc20fc..53706ebe9 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt @@ -1005,6 +1005,19 @@ class EnrichedTextInputView : selection?.validateStyles() } + fun deleteAtSelection() { + runAsATransaction { + val start = selectionStart + val end = selectionEnd + + if (start != end) { + text?.delete(start, end) + } else if (start > 0) { + text?.delete(start - 1, start) + } + } + } + fun requestHTML(requestId: Int) { val html = try { diff --git a/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputViewManager.kt b/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputViewManager.kt index 9dcbb8244..f1b7775c0 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputViewManager.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputViewManager.kt @@ -479,6 +479,10 @@ class EnrichedTextInputViewManager : view?.setTextAlignment(alignment) } + override fun deleteAtSelection(view: EnrichedTextInputView?) { + view?.deleteAtSelection() + } + override fun measure( context: Context, localData: ReadableMap?, diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm index a899aa629..5fc97329e 100644 --- a/ios/EnrichedTextInputView.mm +++ b/ios/EnrichedTextInputView.mm @@ -1296,6 +1296,8 @@ - (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args { if (!_placeholderLabel.isHidden) { [self refreshPlaceholderLabelStyles]; } + } else if ([commandName isEqualToString:@"deleteAtSelection"]) { + [self deleteAtSelection]; } } @@ -1335,6 +1337,18 @@ - (void)setValue:(NSString *)value { [self anyTextMayHaveBeenModified]; } +- (void)deleteAtSelection { + UITextRange *selectedRange = self.textView.selectedTextRange; + + if (selectedRange.empty) { + [self.textView deleteBackward]; + } else { + [self.textView replaceRange:selectedRange withText:@""]; + } + + [self anyTextMayHaveBeenModified]; +} + - (void)setCustomSelection:(NSInteger)visibleStart end:(NSInteger)visibleEnd { NSString *text = textView.textStorage.string; diff --git a/src/native/EnrichedTextInput.tsx b/src/native/EnrichedTextInput.tsx index feffab4cc..e339360a3 100644 --- a/src/native/EnrichedTextInput.tsx +++ b/src/native/EnrichedTextInput.tsx @@ -277,6 +277,9 @@ export const EnrichedTextInput = ({ ) => { Commands.setTextAlignment(nullthrows(nativeRef.current), alignment); }, + deleteAtSelection: () => { + Commands.deleteAtSelection(nullthrows(nativeRef.current)); + }, })); const handleMentionEvent = (e: NativeSyntheticEvent) => { diff --git a/src/spec/EnrichedTextInputNativeComponent.ts b/src/spec/EnrichedTextInputNativeComponent.ts index 20e9d12b4..6044babd1 100644 --- a/src/spec/EnrichedTextInputNativeComponent.ts +++ b/src/spec/EnrichedTextInputNativeComponent.ts @@ -482,6 +482,7 @@ interface NativeCommands { viewRef: React.ElementRef, alignment: string ) => void; + deleteAtSelection: (viewRef: React.ElementRef) => void; } export const Commands: NativeCommands = codegenNativeCommands({ @@ -516,6 +517,7 @@ export const Commands: NativeCommands = codegenNativeCommands({ 'addMention', 'requestHTML', 'setTextAlignment', + 'deleteAtSelection', ], }); diff --git a/src/types.ts b/src/types.ts index acda85b41..0307a35b7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -564,6 +564,8 @@ export interface EnrichedTextInputInstance extends NativeMethods { setTextAlignment: ( alignment: 'left' | 'center' | 'right' | 'justify' | 'auto' ) => void; + + deleteAtSelection: () => void; } export interface ContextMenuItem { diff --git a/src/web/EnrichedTextInput.tsx b/src/web/EnrichedTextInput.tsx index 8ec53d0d7..f1c0dc983 100644 --- a/src/web/EnrichedTextInput.tsx +++ b/src/web/EnrichedTextInput.tsx @@ -376,6 +376,17 @@ export const EnrichedTextInput = ({ runFocused(editor, (c) => c.setTextAlign(alignment)); } }, + deleteAtSelection: () => { + runFocused(editor, (c) => { + const { from, to } = editor.state.selection; + + if (from !== to) { + return c.deleteSelection(); + } else { + return c.deleteRange({ from: from - 1, to }); + } + }); + }, }), [editor, mentionIndicatorsRef, useHtmlNormalizerRef] ); From 2fd8390a5da31dda634d72130d9486e8af0dce40 Mon Sep 17 00:00:00 2001 From: Islam Rustamov Date: Sun, 26 Jul 2026 17:41:16 +0300 Subject: [PATCH 2/4] chore: add comment --- src/types.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/types.ts b/src/types.ts index 0307a35b7..75c5b62e8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -565,6 +565,10 @@ export interface EnrichedTextInputInstance extends NativeMethods { alignment: 'left' | 'center' | 'right' | 'justify' | 'auto' ) => void; + /** + * Deletes text at current selection. If there is no selection - deletes one character + * backwards. + */ deleteAtSelection: () => void; } From 8fbad469b129b4dd246566b5f7f4af9ebccca3e9 Mon Sep 17 00:00:00 2001 From: Islam Rustamov Date: Sat, 1 Aug 2026 18:35:06 +0300 Subject: [PATCH 3/4] chore: refactoring --- .../swmansion/enriched/textinput/EnrichedTextInputView.kt | 4 ++-- ios/EnrichedTextInputView.mm | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt b/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt index 53706ebe9..74597dca8 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt @@ -1007,8 +1007,8 @@ class EnrichedTextInputView : fun deleteAtSelection() { runAsATransaction { - val start = selectionStart - val end = selectionEnd + val start = selectionStart.coerceAtLeast(0) + val end = selectionEnd.coerceAtLeast(0) if (start != end) { text?.delete(start, end) diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm index 5fc97329e..6d85b2d0a 100644 --- a/ios/EnrichedTextInputView.mm +++ b/ios/EnrichedTextInputView.mm @@ -1338,7 +1338,11 @@ - (void)setValue:(NSString *)value { } - (void)deleteAtSelection { - UITextRange *selectedRange = self.textView.selectedTextRange; + UITextRange *selectedRange = textView.selectedTextRange; + + if (selectedRange == nil) { + return; + } if (selectedRange.empty) { [self.textView deleteBackward]; From ba7aa4a555cd48d9260bd64058f71fd58bad6005 Mon Sep 17 00:00:00 2001 From: Islam Rustamov Date: Wed, 19 Aug 2026 19:43:00 +0300 Subject: [PATCH 4/4] fix: update deletion logic to correctly imitate delete button press --- .../textinput/EnrichedTextInputView.kt | 12 ++++++--- ios/EnrichedTextInputView.mm | 25 +++++++++++-------- src/web/EnrichedTextInput.tsx | 19 +++++++++++--- 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt b/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt index 74597dca8..efabb2dd2 100644 --- a/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt +++ b/android/src/main/java/com/swmansion/enriched/textinput/EnrichedTextInputView.kt @@ -1006,14 +1006,18 @@ class EnrichedTextInputView : } fun deleteAtSelection() { - runAsATransaction { + val inputConnection = onCreateInputConnection(EditorInfo()) + + if (inputConnection != null) { val start = selectionStart.coerceAtLeast(0) val end = selectionEnd.coerceAtLeast(0) if (start != end) { - text?.delete(start, end) - } else if (start > 0) { - text?.delete(start - 1, start) + // If we have selection - delete it + inputConnection.commitText("", 1) + } else { + // If we don't have selection - delete previous character + inputConnection.deleteSurroundingText(1, 0) } } } diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm index 6d85b2d0a..50714641d 100644 --- a/ios/EnrichedTextInputView.mm +++ b/ios/EnrichedTextInputView.mm @@ -1338,19 +1338,24 @@ - (void)setValue:(NSString *)value { } - (void)deleteAtSelection { - UITextRange *selectedRange = textView.selectedTextRange; + NSRange range = textView.selectedRange; - if (selectedRange == nil) { - return; - } + // Checking if there is anything to delete + if (range.location > 0 || range.length > 0) { + // If we have no selection - we want to manually set range + // to cover the character before the caret + if (range.length == 0) { + range.location -= 1; + range.length = 1; + } - if (selectedRange.empty) { - [self.textView deleteBackward]; - } else { - [self.textView replaceRange:selectedRange withText:@""]; + // This imitates delete button on keyboard press + if ([textView.delegate textView:textView + shouldChangeTextInRange:range + replacementText:@""]) { + [textView deleteBackward]; + } } - - [self anyTextMayHaveBeenModified]; } - (void)setCustomSelection:(NSInteger)visibleStart end:(NSInteger)visibleEnd { diff --git a/src/web/EnrichedTextInput.tsx b/src/web/EnrichedTextInput.tsx index f1c0dc983..97ebd04a9 100644 --- a/src/web/EnrichedTextInput.tsx +++ b/src/web/EnrichedTextInput.tsx @@ -378,13 +378,24 @@ export const EnrichedTextInput = ({ }, deleteAtSelection: () => { runFocused(editor, (c) => { - const { from, to } = editor.state.selection; + const { from, empty, $from } = editor.state.selection; - if (from !== to) { + // If have selection - we delete it + if (!empty) { return c.deleteSelection(); - } else { - return c.deleteRange({ from: from - 1, to }); } + + const isAtBlockStart = $from.parentOffset === 0; + + if (isAtBlockStart) { + // If caret is in the beginning of the line - we join two lines together + return c.joinBackward(); + } else if (from > 1) { + // If caret is not in the beginning of the line - we delete previous character + return c.deleteRange({ from: from - 1, to: from }); + } + + return c.focus(); }); }, }),