Skip to content

Commit

Permalink
Merge pull request #40128 from software-mansion-labs/@Skalakid/jumpin…
Browse files Browse the repository at this point in the history
…g-composer

Fix jumping composer when entering emojis or markdown text
  • Loading branch information
thienlnam authored Apr 29, 2024
2 parents b1c31c8 + 55429d3 commit 6b204bf
Show file tree
Hide file tree
Showing 16 changed files with 36 additions and 193 deletions.
5 changes: 2 additions & 3 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -868,9 +868,8 @@ const CONST = {
MAX_LINES: 16,
MAX_LINES_SMALL_SCREEN: 6,
MAX_LINES_FULL: -1,

// The minimum number of typed lines needed to enable the full screen composer
FULL_COMPOSER_MIN_LINES: 3,
// The minimum height needed to enable the full screen composer
FULL_COMPOSER_MIN_HEIGHT: 60,
},
MODAL: {
MODAL_TYPE: {
Expand Down
2 changes: 0 additions & 2 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,6 @@ const ONYXKEYS = {
REPORT_ACTIONS_DRAFTS: 'reportActionsDrafts_',
REPORT_ACTIONS_REACTIONS: 'reportActionsReactions_',
REPORT_DRAFT_COMMENT: 'reportDraftComment_',
REPORT_DRAFT_COMMENT_NUMBER_OF_LINES: 'reportDraftCommentNumberOfLines_',
REPORT_IS_COMPOSER_FULL_SIZE: 'reportIsComposerFullSize_',
REPORT_USER_IS_TYPING: 'reportUserIsTyping_',
REPORT_USER_IS_LEAVING_ROOM: 'reportUserIsLeavingRoom_',
Expand Down Expand Up @@ -548,7 +547,6 @@ type OnyxCollectionValuesMapping = {
[ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS]: OnyxTypes.ReportActionsDrafts;
[ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS]: OnyxTypes.ReportActionReactions;
[ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT]: string;
[ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT_NUMBER_OF_LINES]: number;
[ONYXKEYS.COLLECTION.REPORT_IS_COMPOSER_FULL_SIZE]: boolean;
[ONYXKEYS.COLLECTION.REPORT_USER_IS_TYPING]: OnyxTypes.ReportUserIsTyping;
[ONYXKEYS.COLLECTION.REPORT_USER_IS_LEAVING_ROOM]: boolean;
Expand Down
6 changes: 2 additions & 4 deletions src/components/Composer/index.native.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import useResetComposerFocus from '@hooks/useResetComposerFocus';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import * as ComposerUtils from '@libs/ComposerUtils';
import updateIsFullComposerAvailable from '@libs/ComposerUtils/updateIsFullComposerAvailable';
import type {ComposerProps} from './types';

function Composer(
Expand All @@ -21,7 +21,6 @@ function Composer(
isComposerFullSize = false,
setIsFullComposerAvailable = () => {},
autoFocus = false,
isFullComposerAvailable = false,
style,
// 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.
Expand Down Expand Up @@ -75,14 +74,13 @@ function Composer(
placeholderTextColor={theme.placeholderText}
ref={setTextInputRef}
value={value}
onContentSizeChange={(e) => ComposerUtils.updateNumberOfLines({maxLines, isComposerFullSize, isDisabled, setIsFullComposerAvailable}, e, styles)}
onContentSizeChange={(e) => updateIsFullComposerAvailable({maxLines, isComposerFullSize, isDisabled, setIsFullComposerAvailable}, e, styles, true)}
rejectResponderTermination={false}
smartInsertDelete={false}
textAlignVertical="center"
style={[composerStyle, maxHeightStyle]}
markdownStyle={markdownStyle}
autoFocus={autoFocus}
isFullComposerAvailable={isFullComposerAvailable}
/* eslint-disable-next-line react/jsx-props-no-spreading */
{...props}
readOnly={isDisabled}
Expand Down
92 changes: 19 additions & 73 deletions src/components/Composer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ import useMarkdownStyle from '@hooks/useMarkdownStyle';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as Browser from '@libs/Browser';
import * as ComposerUtils from '@libs/ComposerUtils';
import updateIsFullComposerAvailable from '@libs/ComposerUtils/updateIsFullComposerAvailable';
import * as FileUtils from '@libs/fileDownload/FileUtils';
import isEnterWhileComposition from '@libs/KeyboardShortcut/isEnterWhileComposition';
Expand Down Expand Up @@ -58,14 +56,11 @@ function Composer(
style,
shouldClear = false,
autoFocus = false,
isFullComposerAvailable = false,
shouldCalculateCaretPosition = false,
numberOfLines: numberOfLinesProp = 0,
isDisabled = false,
onClear = () => {},
onPasteFile = () => {},
onSelectionChange = () => {},
onNumberOfLinesChange = () => {},
setIsFullComposerAvailable = () => {},
checkComposerVisibility = () => false,
selection: selectionProp = {
Expand All @@ -83,10 +78,8 @@ function Composer(
const styles = useThemeStyles();
const markdownStyle = useMarkdownStyle(value);
const StyleUtils = useStyleUtils();
const {windowWidth} = useWindowDimensions();
const textRef = useRef<HTMLElement & RNText>(null);
const textInput = useRef<AnimatedMarkdownTextInputRef | null>(null);
const [numberOfLines, setNumberOfLines] = useState(numberOfLinesProp);
const [selection, setSelection] = useState<
| {
start: number;
Expand All @@ -109,7 +102,6 @@ function Composer(
return;
}
textInput.current?.clear();
setNumberOfLines(1);
onClear();
}, [shouldClear, onClear]);

Expand All @@ -126,12 +118,8 @@ function Composer(
* Adds the cursor position to the selection change event.
*/
const addCursorPositionToSelectionChange = (event: NativeSyntheticEvent<TextInputSelectionChangeEventData>) => {
if (!isRendered) {
return;
}
const webEvent = event as BaseSyntheticEvent<TextInputSelectionChangeEventData>;

if (shouldCalculateCaretPosition) {
if (shouldCalculateCaretPosition && isRendered) {
// we do flushSync to make sure that the valueBeforeCaret is updated before we calculate the caret position to receive a proper position otherwise we will calculate position for the previous state
flushSync(() => {
setValueBeforeCaret(webEvent.target.value.slice(0, webEvent.nativeEvent.selection.start));
Expand Down Expand Up @@ -236,41 +224,6 @@ function Composer(
[onPasteFile, checkComposerVisibility],
);

/**
* Check the current scrollHeight of the textarea (minus any padding) and
* divide by line height to get the total number of rows for the textarea.
*/
const updateNumberOfLines = useCallback(() => {
if (!textInput.current) {
return;
}
// we reset the height to 0 to get the correct scrollHeight
textInput.current.style.height = '0';
const computedStyle = window.getComputedStyle(textInput.current);
const lineHeight = parseInt(computedStyle.lineHeight, 10) || 20;
const paddingTopAndBottom = parseInt(computedStyle.paddingBottom, 10) + parseInt(computedStyle.paddingTop, 10);
setTextInputWidth(computedStyle.width);

const computedNumberOfLines = ComposerUtils.getNumberOfLines(lineHeight, paddingTopAndBottom, textInput.current.scrollHeight, maxLines);
const generalNumberOfLines = computedNumberOfLines === 0 ? numberOfLinesProp : computedNumberOfLines;

onNumberOfLinesChange(generalNumberOfLines);
updateIsFullComposerAvailable({isFullComposerAvailable, setIsFullComposerAvailable}, generalNumberOfLines);
setNumberOfLines(generalNumberOfLines);
textInput.current.style.height = 'auto';
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [value, maxLines, numberOfLinesProp, onNumberOfLinesChange, isFullComposerAvailable, setIsFullComposerAvailable, windowWidth]);

useEffect(() => {
updateNumberOfLines();
}, [updateNumberOfLines]);

const currentNumberOfLines = useMemo(
() => (isComposerFullSize ? undefined : numberOfLines),

[isComposerFullSize, numberOfLines],
);

useEffect(() => {
if (!textInput.current) {
return;
Expand Down Expand Up @@ -333,7 +286,7 @@ function Composer(
opacity: 0,
}}
>
<Text style={[StyleSheet.flatten([style, styles.noSelect]), numberOfLines < maxLines ? styles.overflowHidden : {}, {maxWidth: textInputWidth as DimensionValue}]}>
<Text style={[StyleSheet.flatten([style, styles.noSelect]), StyleUtils.getComposerMaxHeightStyle(maxLines, isComposerFullSize), {maxWidth: textInputWidth as DimensionValue}]}>
{`${valueBeforeCaret} `}
<Text
numberOfLines={1}
Expand All @@ -349,23 +302,20 @@ function Composer(
if (shouldContainScroll) {
return isScrollBarVisible ? [styles.overflowScroll, styles.overscrollBehaviorContain] : styles.overflowHidden;
}
return [
// We are hiding the scrollbar to prevent it from reducing the text input width,
// so we can get the correct scroll height while calculating the number of lines.
numberOfLines < maxLines ? styles.overflowHidden : {},
];
}, [shouldContainScroll, isScrollBarVisible, maxLines, numberOfLines, styles.overflowHidden, styles.overflowScroll, styles.overscrollBehaviorContain]);
return styles.overflowAuto;
}, [shouldContainScroll, styles.overflowAuto, styles.overflowScroll, styles.overscrollBehaviorContain, styles.overflowHidden, isScrollBarVisible]);

const inputStyleMemo = useMemo(
() => [
StyleSheet.flatten([style, {outline: 'none'}]),
StyleUtils.getComposeTextAreaPadding(numberOfLines, isComposerFullSize),
StyleUtils.getComposeTextAreaPadding(isComposerFullSize),
Browser.isMobileSafari() || Browser.isSafari() ? styles.rtlTextRenderForSafari : {},
scrollStyleMemo,
StyleUtils.getComposerMaxHeightStyle(maxLines, isComposerFullSize),
isComposerFullSize ? ({height: '100%', maxHeight: 'none' as DimensionValue} as TextStyle) : undefined,
],

[numberOfLines, scrollStyleMemo, styles.rtlTextRenderForSafari, style, StyleUtils, isComposerFullSize],
[style, styles.rtlTextRenderForSafari, scrollStyleMemo, StyleUtils, maxLines, isComposerFullSize],
);

return (
Expand All @@ -376,32 +326,28 @@ function Composer(
placeholderTextColor={theme.placeholderText}
ref={(el) => (textInput.current = el)}
selection={selection}
style={inputStyleMemo}
style={[inputStyleMemo]}
markdownStyle={markdownStyle}
value={value}
defaultValue={defaultValue}
autoFocus={autoFocus}
/* eslint-disable-next-line react/jsx-props-no-spreading */
{...props}
onSelectionChange={addCursorPositionToSelectionChange}
numberOfLines={currentNumberOfLines}
onContentSizeChange={(e) => {
setTextInputWidth(`${e.nativeEvent.contentSize.width}px`);
updateIsFullComposerAvailable({maxLines, isComposerFullSize, isDisabled, setIsFullComposerAvailable}, e, styles);
}}
disabled={isDisabled}
onKeyPress={handleKeyPress}
onFocus={(e) => {
if (isReportActionCompose) {
ReportActionComposeFocusManager.onComposerFocus(null);
} else {
// While a user edits a comment, if they open the LHN menu, we want to ensure that
// the focus returns to the message edit composer after they click on a menu item (e.g. mark as read).
// To achieve this, we re-assign the focus callback here.
ReportActionComposeFocusManager.onComposerFocus(() => {
if (!textInput.current) {
return;
}

textInput.current.focus();
});
}
ReportActionComposeFocusManager.onComposerFocus(() => {
if (!textInput.current) {
return;
}

textInput.current.focus();
});

props.onFocus?.(e);
}}
Expand Down
6 changes: 0 additions & 6 deletions src/components/Composer/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,9 @@ type ComposerProps = TextInputProps & {
/** The value of the comment box */
value?: string;

/** Number of lines for the comment */
numberOfLines?: number;

/** Callback method handle when the input is changed */
onChangeText?: (numberOfLines: string) => void;

/** Callback method to update number of lines for the comment */
onNumberOfLinesChange?: (numberOfLines: number) => void;

/** Callback method to handle pasting a file */
onPasteFile?: (file: File) => void;

Expand Down
8 changes: 0 additions & 8 deletions src/libs/ComposerUtils/getNumberOfLines/index.native.ts

This file was deleted.

12 changes: 0 additions & 12 deletions src/libs/ComposerUtils/getNumberOfLines/index.ts

This file was deleted.

3 changes: 0 additions & 3 deletions src/libs/ComposerUtils/getNumberOfLines/types.ts

This file was deleted.

4 changes: 1 addition & 3 deletions src/libs/ComposerUtils/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import getNumberOfLines from './getNumberOfLines';
import updateNumberOfLines from './updateNumberOfLines';

type Selection = {
start: number;
Expand Down Expand Up @@ -49,5 +47,5 @@ function findCommonSuffixLength(str1: string, str2: string, cursorPosition: numb
return commonSuffixLength;
}

export {getNumberOfLines, updateNumberOfLines, insertText, canSkipTriggerHotkeys, insertWhiteSpaceAtIndex, findCommonSuffixLength};
export {insertText, canSkipTriggerHotkeys, insertWhiteSpaceAtIndex, findCommonSuffixLength};
export type {Selection};
12 changes: 10 additions & 2 deletions src/libs/ComposerUtils/updateIsFullComposerAvailable.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import type {NativeSyntheticEvent, TextInputContentSizeChangeEventData} from 'react-native';
import type {ComposerProps} from '@components/Composer/types';
import type {ThemeStyles} from '@styles/index';
import CONST from '@src/CONST';

/**
* Update isFullComposerAvailable if needed
* @param numberOfLines The number of lines in the text input
*/
function updateIsFullComposerAvailable(props: ComposerProps, numberOfLines: number) {
const isFullComposerAvailable = numberOfLines >= CONST.COMPOSER.FULL_COMPOSER_MIN_LINES;
function updateIsFullComposerAvailable(props: ComposerProps, event: NativeSyntheticEvent<TextInputContentSizeChangeEventData>, styles: ThemeStyles, shouldIncludePadding = false) {
const paddingTopAndBottom = shouldIncludePadding ? styles.textInputComposeSpacing.paddingVertical * 2 : 0;
const inputHeight = event?.nativeEvent?.contentSize?.height ?? null;
if (!inputHeight) {
return;
}
const totalHeight = inputHeight + paddingTopAndBottom;
const isFullComposerAvailable = totalHeight >= CONST.COMPOSER.FULL_COMPOSER_MIN_HEIGHT;
if (isFullComposerAvailable !== props.isFullComposerAvailable) {
props.setIsFullComposerAvailable?.(isFullComposerAvailable);
}
Expand Down
20 changes: 0 additions & 20 deletions src/libs/ComposerUtils/updateNumberOfLines/index.native.ts

This file was deleted.

5 changes: 0 additions & 5 deletions src/libs/ComposerUtils/updateNumberOfLines/index.ts

This file was deleted.

7 changes: 0 additions & 7 deletions src/libs/ComposerUtils/updateNumberOfLines/types.ts

This file was deleted.

12 changes: 0 additions & 12 deletions src/libs/actions/Report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1171,11 +1171,6 @@ function saveReportDraftComment(reportID: string, comment: string | null) {
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`, prepareDraftComment(comment));
}

/** Saves the number of lines for the comment */
function saveReportCommentNumberOfLines(reportID: string, numberOfLines: number) {
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT_NUMBER_OF_LINES}${reportID}`, numberOfLines);
}

/** Broadcasts whether or not a user is typing on a report over the report's private pusher channel. */
function broadcastUserIsTyping(reportID: string) {
const privateReportChannelName = getReportChannelName(reportID);
Expand Down Expand Up @@ -1508,11 +1503,6 @@ function saveReportActionDraft(reportID: string, reportAction: ReportAction, dra
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS}${originalReportID}`, {[reportAction.reportActionID]: {message: draftMessage}});
}

/** Saves the number of lines for the report action draft */
function saveReportActionDraftNumberOfLines(reportID: string, reportActionID: string, numberOfLines: number) {
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT_NUMBER_OF_LINES}${reportID}_${reportActionID}`, numberOfLines);
}

function updateNotificationPreference(
reportID: string,
previousValue: NotificationPreference | undefined,
Expand Down Expand Up @@ -3726,15 +3716,13 @@ export {
unsubscribeFromReportChannel,
unsubscribeFromLeavingRoomReportChannel,
saveReportDraftComment,
saveReportCommentNumberOfLines,
broadcastUserIsTyping,
broadcastUserIsLeavingRoom,
togglePinnedState,
editReportComment,
handleUserDeletedLinksInHtml,
deleteReportActionDraft,
saveReportActionDraft,
saveReportActionDraftNumberOfLines,
deleteReportComment,
navigateToConciergeChat,
addPolicyReport,
Expand Down
Loading

0 comments on commit 6b204bf

Please sign in to comment.