From 3ce4b80e6fc8f86b0eaa89b2cbc7a537018ee573 Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Tue, 15 Oct 2024 16:24:53 -0700 Subject: [PATCH] Include existing attributes in newly typed text (#47018) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/47018 This change makes it so that newly typed text in a TextInput will include the existing attributes present based on cursor position. E.g. if you type after an inner fragment with blue text, the next character will be blue (or, an event emitter specific to an inner fragment will also be expanded). This is a behavior change for the (admittedly rare) case of uncontrolled TextInput with initially present children AttributedText, but more often effect controlled components, before state update (we are after, less likely to need to reset AttributedString because of mismatch). Originally included this in D64121570, but it's not needed to fix the common case since we include paragraph-level event emitter as part of default attributes, and has some of its own risk, so I decided it is better separate. Changelog: [iOS][Changed] - Include existing attributes in newly typed text Reviewed By: cipolleschi Differential Revision: D64352310 fbshipit-source-id: 90ef8c49f50186eadf777e81cf6af57e1aada207 --- .../TextInput/RCTTextInputComponentView.mm | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm index 8c532d85502bc1..59aa194706916d 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm @@ -418,6 +418,7 @@ - (void)textInputDidChange - (void)textInputDidChangeSelection { + [self _updateTypingAttributes]; if (_comingFromJS) { return; } @@ -674,9 +675,26 @@ - (void)_setAttributedString:(NSAttributedString *)attributedString [_backedTextInputView scrollRangeToVisible:NSMakeRange(offsetStart, 0)]; } [self _restoreTextSelection]; + [self _updateTypingAttributes]; _lastStringStateWasUpdatedWith = attributedString; } +// Ensure that newly typed text will inherit any custom attributes. We follow the logic of RN Android, where attributes +// to the left of the cursor are copied into new text, unless we are at the start of the field, in which case we will +// copy the attributes from text to the right. This allows consistency between backed input and new AttributedText +// https://github.com/facebook/react-native/blob/3102a58df38d96f3dacef0530e4dbb399037fcd2/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/internal/span/SetSpanOperation.kt#L30 +- (void)_updateTypingAttributes +{ + if (_backedTextInputView.attributedText.length > 0) { + NSUInteger offsetStart = [_backedTextInputView offsetFromPosition:_backedTextInputView.beginningOfDocument + toPosition:_backedTextInputView.selectedTextRange.start]; + + NSUInteger samplePoint = offsetStart == 0 ? 0 : offsetStart - 1; + _backedTextInputView.typingAttributes = [_backedTextInputView.attributedText attributesAtIndex:samplePoint + effectiveRange:NULL]; + } +} + - (void)_setMultiline:(BOOL)multiline { [_backedTextInputView removeFromSuperview];