Skip to content

Commit 99d40da

Browse files
feat: alternate setText that supports undo operation
1 parent d0f27ba commit 99d40da

File tree

3 files changed

+122
-1
lines changed

3 files changed

+122
-1
lines changed

ios/TypeRichTextInputView.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ NS_ASSUME_NONNULL_BEGIN
2222

2323
// helpers used by commands
2424
- (BOOL)isTouchInProgress;
25+
//- (BOOL)isHandlingUserInput;
2526
- (void)invalidateTextLayoutFromCommand;
2627
- (void)updatePlaceholderVisibilityFromCommand;
2728
- (void)dispatchSelectionChangeIfNeeded;
29+
2830
@end
2931
NS_ASSUME_NONNULL_END

ios/TypeRichTextInputView.mm

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ @implementation TypeRichTextInputView {
5151

5252
/// Commands to call from js side
5353
TypeRichTextInputCommands *_commandHandler;
54+
// BOOL _isHandlingUserInput;
5455
}
5556

5657
#pragma mark - Fabric registration
@@ -590,6 +591,7 @@ - (void)updateState:(State::Shared const &)state
590591
- (void)textViewDidChange:(UITextView *)textView {
591592

592593
if (self.blockEmitting) return;
594+
// _isHandlingUserInput = YES;
593595

594596
[self updatePlaceholderVisibility];
595597

@@ -608,6 +610,10 @@ - (void)textViewDidChange:(UITextView *)textView {
608610

609611
[self updatePlaceholderVisibilityFromCommand];
610612
[self invalidateTextLayoutDuringTyping];
613+
614+
// dispatch_async(dispatch_get_main_queue(), ^{
615+
// self->_isHandlingUserInput = NO;
616+
// });
611617
}
612618

613619
#pragma mark -- focus / blur event
@@ -804,4 +810,9 @@ - (void)dispatchSelectionChangeIfNeeded {
804810
.text = std::string(tv.text.UTF8String ?: "")
805811
});
806812
}
813+
814+
//- (BOOL)isHandlingUserInput {
815+
// return _isHandlingUserInput;
816+
//}
817+
807818
@end

ios/modules/commands/TypeRichTextInputCommands.mm

Lines changed: 109 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,86 @@ - (void)setText:(NSString *)text
120120
});
121121
}
122122

123+
/// setText(text) — undoable, cursor-safe, IME-safe
124+
//- (void)setText:(NSString *)text
125+
//{
126+
// UITextView *tv = _textView;
127+
// TypeRichTextInputView *owner = _owner;
128+
// if (!tv || !owner) return;
129+
//
130+
// dispatch_async(dispatch_get_main_queue(), ^{
131+
// // Never touch text while IME composing
132+
// if (tv.markedTextRange) return;
133+
//
134+
// NSString *newText = text ?: @"";
135+
// NSString *oldText = tv.text ?: @"";
136+
//
137+
// // No-op fast path
138+
// if ([oldText isEqualToString:newText]) {
139+
// return;
140+
// }
141+
//
142+
// owner.blockEmitting = YES;
143+
//
144+
// // Save selection (cursor)
145+
// NSRange oldSelection = tv.selectedRange;
146+
//
147+
// // Compute minimal diff range
148+
// NSRange replaceRange = DiffRange(oldText, newText);
149+
//
150+
// NSInteger insertStart = replaceRange.location;
151+
// NSInteger insertLength =
152+
// newText.length - (oldText.length - replaceRange.length);
153+
//
154+
// NSString *insertText =
155+
// insertLength > 0
156+
// ? [newText substringWithRange:
157+
// NSMakeRange(insertStart, insertLength)]
158+
// : @"";
159+
//
160+
// // Convert NSRange → UITextRange
161+
// UITextPosition *start =
162+
// [tv positionFromPosition:tv.beginningOfDocument
163+
// offset:replaceRange.location];
164+
// UITextPosition *end =
165+
// [tv positionFromPosition:tv.beginningOfDocument
166+
// offset:NSMaxRange(replaceRange)];
167+
//
168+
// if (!start || !end) {
169+
// owner.blockEmitting = NO;
170+
// return;
171+
// }
172+
//
173+
// UITextRange *uiRange =
174+
// [tv textRangeFromPosition:start toPosition:end];
175+
//
176+
// // THIS IS THE KEY LINE (undo-safe)
177+
// [tv replaceRange:uiRange withText:insertText];
178+
//
179+
// // ---- Restore selection correctly ----
180+
// NSInteger delta = insertText.length - replaceRange.length;
181+
//
182+
// NSInteger newLoc = oldSelection.location;
183+
// NSInteger newLen = oldSelection.length;
184+
//
185+
// if (oldSelection.location > replaceRange.location) {
186+
// newLoc = MAX(0, newLoc + delta);
187+
// }
188+
//
189+
// NSInteger max = tv.text.length;
190+
// newLoc = MIN(newLoc, max);
191+
// newLen = MIN(newLen, max - newLoc);
192+
//
193+
// tv.selectedRange = NSMakeRange(newLoc, newLen);
194+
//
195+
// owner.blockEmitting = NO;
196+
//
197+
// [owner updatePlaceholderVisibilityFromCommand];
198+
// [owner invalidateTextLayoutFromCommand];
199+
// [owner dispatchSelectionChangeIfNeeded];
200+
// });
201+
//}
202+
123203

124204
/// setSelection(start, end)
125205
- (void)setSelectionStart:(NSInteger)start end:(NSInteger)end {
@@ -155,7 +235,8 @@ - (void)insertTextAtStart:(NSInteger)start
155235
dispatch_async(dispatch_get_main_queue(), ^{
156236
if ([owner isTouchInProgress]) return;
157237
if (tv.markedTextRange) return;
158-
238+
// if ([owner isHandlingUserInput]) return;
239+
159240
owner.blockEmitting = YES;
160241

161242
UITextPosition *s =
@@ -199,4 +280,31 @@ - (NSDictionary *)baseAttributesForTextView:(UITextView *)tv {
199280
NSForegroundColorAttributeName: tv.textColor ?: UIColor.blackColor
200281
};
201282
}
283+
284+
static NSRange DiffRange(NSString *oldText, NSString *newText) {
285+
NSInteger oldLen = oldText.length;
286+
NSInteger newLen = newText.length;
287+
288+
NSInteger start = 0;
289+
while (start < oldLen &&
290+
start < newLen &&
291+
[oldText characterAtIndex:start] ==
292+
[newText characterAtIndex:start]) {
293+
start++;
294+
}
295+
296+
NSInteger endOld = oldLen;
297+
NSInteger endNew = newLen;
298+
299+
while (endOld > start &&
300+
endNew > start &&
301+
[oldText characterAtIndex:endOld - 1] ==
302+
[newText characterAtIndex:endNew - 1]) {
303+
endOld--;
304+
endNew--;
305+
}
306+
307+
return NSMakeRange(start, endOld - start);
308+
}
309+
202310
@end

0 commit comments

Comments
 (0)