From c221cdfd9316d7d14f5953a7e6d078591ac3be84 Mon Sep 17 00:00:00 2001
From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com>
Date: Wed, 19 Apr 2023 20:58:14 +0100
Subject: [PATCH 1/2] Make Composer no longer selection controlled
---
src/components/Composer/index.android.js | 10 ----
src/components/Composer/index.ios.js | 17 +------
src/components/Composer/index.js | 16 ------
src/pages/home/report/ReportActionCompose.js | 51 +++++++++----------
.../report/ReportActionItemMessageEdit.js | 34 ++++++-------
5 files changed, 40 insertions(+), 88 deletions(-)
diff --git a/src/components/Composer/index.android.js b/src/components/Composer/index.android.js
index 603bdf829edd..83dc6be587a1 100644
--- a/src/components/Composer/index.android.js
+++ b/src/components/Composer/index.android.js
@@ -24,12 +24,6 @@ const propTypes = {
/** Prevent edits and interactions like focus for this input. */
isDisabled: PropTypes.bool,
- /** Selection Object */
- selection: PropTypes.shape({
- start: PropTypes.number,
- end: PropTypes.number,
- }),
-
/** Whether the full composer can be opened */
isFullComposerAvailable: PropTypes.bool,
@@ -51,10 +45,6 @@ const defaultProps = {
autoFocus: false,
isDisabled: false,
forwardedRef: null,
- selection: {
- start: 0,
- end: 0,
- },
isFullComposerAvailable: false,
setIsFullComposerAvailable: () => {},
isComposerFullSize: false,
diff --git a/src/components/Composer/index.ios.js b/src/components/Composer/index.ios.js
index b31a5b462f50..83dc6be587a1 100644
--- a/src/components/Composer/index.ios.js
+++ b/src/components/Composer/index.ios.js
@@ -24,12 +24,6 @@ const propTypes = {
/** Prevent edits and interactions like focus for this input. */
isDisabled: PropTypes.bool,
- /** Selection Object */
- selection: PropTypes.shape({
- start: PropTypes.number,
- end: PropTypes.number,
- }),
-
/** Whether the full composer can be opened */
isFullComposerAvailable: PropTypes.bool,
@@ -51,10 +45,6 @@ const defaultProps = {
autoFocus: false,
isDisabled: false,
forwardedRef: null,
- selection: {
- start: 0,
- end: 0,
- },
isFullComposerAvailable: false,
setIsFullComposerAvailable: () => {},
isComposerFullSize: false,
@@ -92,11 +82,6 @@ class Composer extends React.Component {
}
render() {
- // On native layers we like to have the Text Input not focused so the
- // user can read new chats without the keyboard in the way of the view.
- // On Android, the selection prop is required on the TextInput but this prop has issues on IOS
- // https://github.com/facebook/react-native/issues/29063
- const propsToPass = _.omit(this.props, 'selection');
return (
);
diff --git a/src/components/Composer/index.js b/src/components/Composer/index.js
index 9b2d028a7350..5994e3f84502 100755
--- a/src/components/Composer/index.js
+++ b/src/components/Composer/index.js
@@ -58,12 +58,6 @@ const propTypes = {
/** Update selection position on change */
onSelectionChange: PropTypes.func,
- /** Selection Object */
- selection: PropTypes.shape({
- start: PropTypes.number,
- end: PropTypes.number,
- }),
-
/** Whether the full composer can be opened */
isFullComposerAvailable: PropTypes.bool,
@@ -92,10 +86,6 @@ const defaultProps = {
autoFocus: false,
forwardedRef: null,
onSelectionChange: () => {},
- selection: {
- start: 0,
- end: 0,
- },
isFullComposerAvailable: false,
setIsFullComposerAvailable: () => {},
isComposerFullSize: false,
@@ -174,11 +164,6 @@ class Composer extends React.Component {
|| prevProps.numberOfLines !== this.props.numberOfLines) {
this.updateNumberOfLines();
}
-
- if (prevProps.selection !== this.props.selection) {
- // eslint-disable-next-line react/no-did-update-set-state
- this.setState({selection: this.props.selection});
- }
}
componentWillUnmount() {
@@ -365,7 +350,6 @@ class Composer extends React.Component {
autoCorrect={!Browser.isMobileSafari()}
placeholderTextColor={themeColors.placeholderText}
ref={el => this.textInput = el}
- selection={this.state.selection}
onChange={this.shouldCallUpdateNumberOfLines}
onSelectionChange={this.onSelectionChange}
style={[
diff --git a/src/pages/home/report/ReportActionCompose.js b/src/pages/home/report/ReportActionCompose.js
index 1fa0874824d8..ee7bbd429a33 100644
--- a/src/pages/home/report/ReportActionCompose.js
+++ b/src/pages/home/report/ReportActionCompose.js
@@ -53,6 +53,7 @@ import ArrowKeyFocusManager from '../../../components/ArrowKeyFocusManager';
import OfflineWithFeedback from '../../../components/OfflineWithFeedback';
import KeyboardShortcut from '../../../libs/KeyboardShortcut';
import Permissions from '../../../libs/Permissions';
+import setSelection from '../../../libs/setSelection';
const propTypes = {
/** Beta features list */
@@ -479,15 +480,12 @@ class ReportActionCompose extends React.Component {
const emojiObject = this.state.suggestedEmojis[highlightedEmojiIndex];
const emojiCode = emojiObject.types && emojiObject.types[this.props.preferredSkinTone] ? emojiObject.types[this.props.preferredSkinTone] : emojiObject.code;
const commentAfterColonWithEmojiNameRemoved = this.state.value.slice(this.state.selection.end).replace(CONST.REGEX.EMOJI_REPLACER, CONST.SPACE);
+ const oldColonIndex = this.state.colonIndex;
this.updateComment(`${commentBeforeColon}${emojiCode} ${commentAfterColonWithEmojiNameRemoved}`, true);
- this.setState(prevState => ({
- selection: {
- start: prevState.colonIndex + emojiCode.length + CONST.SPACE_LENGTH,
- end: prevState.colonIndex + emojiCode.length + CONST.SPACE_LENGTH,
- },
- suggestedEmojis: [],
- }));
+ this.setState({suggestedEmojis: []}, () => {
+ setSelection(this.textInput, oldColonIndex + emojiCode.length + CONST.SPACE_LENGTH, oldColonIndex + emojiCode.length + CONST.SPACE_LENGTH);
+ });
EmojiUtils.addToFrequentlyUsedEmojis(this.props.frequentlyUsedEmojis, emojiObject);
}
@@ -505,13 +503,11 @@ class ReportActionCompose extends React.Component {
const newComment = this.comment.slice(0, this.state.selection.start)
+ emojiWithSpace
+ this.comment.slice(this.state.selection.end, this.comment.length);
- this.setState(prevState => ({
- selection: {
- start: prevState.selection.start + emojiWithSpace.length,
- end: prevState.selection.start + emojiWithSpace.length,
- },
- }));
- this.updateComment(newComment);
+ const newSelection = {
+ start: this.state.selection.start + emojiWithSpace.length,
+ end: this.state.selection.start + emojiWithSpace.length,
+ };
+ this.updateComment(newComment, false, newSelection);
}
/**
@@ -562,22 +558,22 @@ class ReportActionCompose extends React.Component {
*
* @param {String} comment
* @param {Boolean} shouldDebounceSaveComment
+ * @param {Object} selection
*/
- updateComment(comment, shouldDebounceSaveComment) {
+ updateComment(comment, shouldDebounceSaveComment, selection) {
+ const oldComment = this.state.value;
+ const oldSelection = this.state.selection;
const newComment = EmojiUtils.replaceEmojis(comment, this.props.isSmallScreenWidth, this.props.preferredSkinTone);
- this.setState((prevState) => {
- const newState = {
- isCommentEmpty: !!newComment.match(/^(\s)*$/),
- value: newComment,
- };
- if (comment !== newComment) {
- const remainder = prevState.value.slice(prevState.selection.end).length;
- newState.selection = {
- start: newComment.length - remainder,
- end: newComment.length - remainder,
- };
+ this.setState({
+ isCommentEmpty: !!newComment.match(/^(\s)*$/),
+ value: newComment,
+ }, () => {
+ if (selection) {
+ setSelection(this.textInput, selection.start, selection.end);
+ } else if (comment !== newComment) {
+ const remainder = oldComment.slice(oldSelection.end).length;
+ setSelection(this.textInput, newComment.length - remainder, newComment.length - remainder);
}
- return newState;
});
// Indicate that draft has been created.
@@ -879,7 +875,6 @@ class ReportActionCompose extends React.Component {
shouldClear={this.state.textInputShouldClear}
onClear={() => this.setTextInputShouldClear(false)}
isDisabled={isComposeDisabled || isBlockedFromConcierge || this.props.disabled}
- selection={this.state.selection}
onSelectionChange={this.onSelectionChange}
isFullComposerAvailable={this.state.isFullComposerAvailable}
setIsFullComposerAvailable={this.setIsFullComposerAvailable}
diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.js
index db037c4122b0..fb723d52acb5 100644
--- a/src/pages/home/report/ReportActionItemMessageEdit.js
+++ b/src/pages/home/report/ReportActionItemMessageEdit.js
@@ -27,6 +27,7 @@ import withWindowDimensions, {windowDimensionsPropTypes} from '../../../componen
import withLocalize, {withLocalizePropTypes} from '../../../components/withLocalize';
import withKeyboardState, {keyboardStatePropTypes} from '../../../components/withKeyboardState';
import ONYXKEYS from '../../../ONYXKEYS';
+import setSelection from '../../../libs/setSelection';
const propTypes = {
/** All the data of the action */
@@ -135,19 +136,19 @@ class ReportActionItemMessageEdit extends React.Component {
* Update the value of the draft in Onyx
*
* @param {String} draft
+ * @param {Object} selection
*/
- updateDraft(draft) {
+ updateDraft(draft, selection) {
+ const oldDraft = this.state.draft;
+ const oldSelection = this.state.selection;
const newDraft = EmojiUtils.replaceEmojis(draft, this.props.isSmallScreenWidth, this.props.preferredSkinTone);
- this.setState((prevState) => {
- const newState = {draft: newDraft};
- if (draft !== newDraft) {
- const remainder = prevState.draft.slice(prevState.selection.end).length;
- newState.selection = {
- start: newDraft.length - remainder,
- end: newDraft.length - remainder,
- };
+ this.setState({draft: newDraft}, () => {
+ if (selection) {
+ setSelection(this.textInput, selection.start, selection.end);
+ } else if (draft !== newDraft) {
+ const remainder = oldDraft.slice(oldSelection.end).length;
+ setSelection(this.textInput, newDraft.length - remainder, newDraft.length - remainder);
}
- return newState;
});
// This component is rendered only when draft is set to a non-empty string. In order to prevent component
@@ -231,13 +232,11 @@ class ReportActionItemMessageEdit extends React.Component {
addEmojiToTextBox(emoji) {
const newComment = this.state.draft.slice(0, this.state.selection.start)
+ emoji + this.state.draft.slice(this.state.selection.end, this.state.draft.length);
- this.setState(prevState => ({
- selection: {
- start: prevState.selection.start + emoji.length,
- end: prevState.selection.start + emoji.length,
- },
- }));
- this.updateDraft(newComment);
+ const newSelection = {
+ start: this.state.selection.start + emoji.length,
+ end: this.state.selection.start + emoji.length,
+ };
+ this.updateDraft(newComment, newSelection);
}
/**
@@ -302,7 +301,6 @@ class ReportActionItemMessageEdit extends React.Component {
}
openReportActionComposeViewWhenClosingMessageEdit(this.props.isSmallScreenWidth);
}}
- selection={this.state.selection}
onSelectionChange={this.onSelectionChange}
numberOfLines={this.props.numberOfLines}
onNumberOfLinesChange={this.updateNumberOfLines}
From 137e8fc93d8303e8c3329906b7add872f43953c0 Mon Sep 17 00:00:00 2001
From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com>
Date: Fri, 21 Apr 2023 10:19:02 +0100
Subject: [PATCH 2/2] Composer: merged index.ios.js and index.android.js into
index.native.js
---
src/components/Composer/index.ios.js | 109 ------------------
.../{index.android.js => index.native.js} | 0
2 files changed, 109 deletions(-)
delete mode 100644 src/components/Composer/index.ios.js
rename src/components/Composer/{index.android.js => index.native.js} (100%)
diff --git a/src/components/Composer/index.ios.js b/src/components/Composer/index.ios.js
deleted file mode 100644
index 83dc6be587a1..000000000000
--- a/src/components/Composer/index.ios.js
+++ /dev/null
@@ -1,109 +0,0 @@
-import React from 'react';
-import {StyleSheet} from 'react-native';
-import PropTypes from 'prop-types';
-import _ from 'underscore';
-import RNTextInput from '../RNTextInput';
-import themeColors from '../../styles/themes/default';
-import CONST from '../../CONST';
-import * as ComposerUtils from '../../libs/ComposerUtils';
-
-const propTypes = {
- /** If the input should clear, it actually gets intercepted instead of .clear() */
- shouldClear: PropTypes.bool,
-
- /** A ref to forward to the text input */
- forwardedRef: PropTypes.func,
-
- /** When the input has cleared whoever owns this input should know about it */
- onClear: PropTypes.func,
-
- /** Set focus to this component the first time it renders.
- * Override this in case you need to set focus on one field out of many, or when you want to disable autoFocus */
- autoFocus: PropTypes.bool,
-
- /** Prevent edits and interactions like focus for this input. */
- isDisabled: PropTypes.bool,
-
- /** Whether the full composer can be opened */
- isFullComposerAvailable: PropTypes.bool,
-
- /** Allow the full composer to be opened */
- setIsFullComposerAvailable: PropTypes.func,
-
- /** Whether the composer is full size */
- isComposerFullSize: PropTypes.bool,
-
- /** General styles to apply to the text input */
- // eslint-disable-next-line react/forbid-prop-types
- style: PropTypes.any,
-
-};
-
-const defaultProps = {
- shouldClear: false,
- onClear: () => {},
- autoFocus: false,
- isDisabled: false,
- forwardedRef: null,
- isFullComposerAvailable: false,
- setIsFullComposerAvailable: () => {},
- isComposerFullSize: false,
- style: null,
-};
-
-class Composer extends React.Component {
- constructor(props) {
- super(props);
-
- this.state = {
- propStyles: StyleSheet.flatten(this.props.style),
- };
- }
-
- componentDidMount() {
- // This callback prop is used by the parent component using the constructor to
- // get a ref to the inner textInput element e.g. if we do
- // this.textInput = el} /> this will not
- // return a ref to the component, but rather the HTML element by default
- if (!this.props.forwardedRef || !_.isFunction(this.props.forwardedRef)) {
- return;
- }
-
- this.props.forwardedRef(this.textInput);
- }
-
- componentDidUpdate(prevProps) {
- if (prevProps.shouldClear || !this.props.shouldClear) {
- return;
- }
-
- this.textInput.clear();
- this.props.onClear();
- }
-
- render() {
- return (
- this.textInput = el}
- maxHeight={this.props.isComposerFullSize ? '100%' : CONST.COMPOSER_MAX_HEIGHT}
- onContentSizeChange={e => ComposerUtils.updateNumberOfLines(this.props, e)}
- rejectResponderTermination={false}
- textAlignVertical="center"
- style={this.state.propStyles}
- /* eslint-disable-next-line react/jsx-props-no-spreading */
- {...this.props}
- editable={!this.props.isDisabled}
- />
- );
- }
-}
-
-Composer.propTypes = propTypes;
-Composer.defaultProps = defaultProps;
-
-export default React.forwardRef((props, ref) => (
- /* eslint-disable-next-line react/jsx-props-no-spreading */
-
-));
diff --git a/src/components/Composer/index.android.js b/src/components/Composer/index.native.js
similarity index 100%
rename from src/components/Composer/index.android.js
rename to src/components/Composer/index.native.js