diff --git a/src/components/Composer/types.ts b/src/components/Composer/types.ts index bfdcb6715d40..d8d88970ea78 100644 --- a/src/components/Composer/types.ts +++ b/src/components/Composer/types.ts @@ -6,6 +6,12 @@ type TextSelection = { }; type ComposerProps = { + /** identify id in the text input */ + id?: string; + + /** Indicate whether input is multiline */ + multiline?: boolean; + /** Maximum number of lines in the text input */ maxLines?: number; @@ -18,6 +24,9 @@ type ComposerProps = { /** 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; @@ -69,6 +78,8 @@ type ComposerProps = { onFocus?: (event: NativeSyntheticEvent) => void; + onBlur?: (event: NativeSyntheticEvent) => void; + /** Should make the input only scroll inside the element avoid scroll out to parent */ shouldContainScroll?: boolean; }; diff --git a/src/components/HeaderWithBackButton/index.tsx b/src/components/HeaderWithBackButton/index.tsx index 209803f2a5d1..6cbfde0645de 100755 --- a/src/components/HeaderWithBackButton/index.tsx +++ b/src/components/HeaderWithBackButton/index.tsx @@ -61,7 +61,6 @@ function HeaderWithBackButton({ const StyleUtils = useStyleUtils(); const [isDownloadButtonActive, temporarilyDisableDownloadButton] = useThrottledButtonState(); const {translate} = useLocalize(); - // @ts-expect-error TODO: Remove this once useKeyboardState (https://github.com/Expensify/App/issues/24941) is migrated to TypeScript. const {isKeyboardShown} = useKeyboardState(); const waitForNavigate = useWaitForNavigation(); diff --git a/src/components/withKeyboardState.tsx b/src/components/withKeyboardState.tsx index 2a74fd3e738e..74d10945fbcb 100755 --- a/src/components/withKeyboardState.tsx +++ b/src/components/withKeyboardState.tsx @@ -16,7 +16,9 @@ const keyboardStatePropTypes = { isKeyboardShown: PropTypes.bool.isRequired, }; -const KeyboardStateContext = createContext(null); +const KeyboardStateContext = createContext({ + isKeyboardShown: false, +}); function KeyboardStateProvider({children}: ChildrenProps): ReactElement | null { const [isKeyboardShown, setIsKeyboardShown] = useState(false); diff --git a/src/hooks/useKeyboardState.ts b/src/hooks/useKeyboardState.ts index 439f626ddcdd..60ad3b8975b1 100644 --- a/src/hooks/useKeyboardState.ts +++ b/src/hooks/useKeyboardState.ts @@ -6,6 +6,6 @@ import {KeyboardStateContext} from '@components/withKeyboardState'; * Hook for getting current state of keyboard * whether the keyboard is open */ -export default function useKeyboardState(): KeyboardStateContextValue | null { +export default function useKeyboardState(): KeyboardStateContextValue { return useContext(KeyboardStateContext); } diff --git a/src/libs/EmojiUtils.ts b/src/libs/EmojiUtils.ts index 02d1b34c69c1..e34fa0b90fc6 100644 --- a/src/libs/EmojiUtils.ts +++ b/src/libs/EmojiUtils.ts @@ -306,7 +306,7 @@ function getAddedEmojis(currentEmojis: Emoji[], formerEmojis: Emoji[]): Emoji[] * Replace any emoji name in a text with the emoji icon. * If we're on mobile, we also add a space after the emoji granted there's no text after it. */ -function replaceEmojis(text: string, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE, lang: 'en' | 'es' = CONST.LOCALES.DEFAULT): ReplacedEmoji { +function replaceEmojis(text: string, preferredSkinTone: number = CONST.EMOJI_DEFAULT_SKIN_TONE, lang: Locale = CONST.LOCALES.DEFAULT): ReplacedEmoji { // emojisTrie is importing the emoji JSON file on the app starting and we want to avoid it const emojisTrie = require('./EmojiTrie').default; @@ -370,7 +370,7 @@ function replaceEmojis(text: string, preferredSkinTone = CONST.EMOJI_DEFAULT_SKI /** * Find all emojis in a text and replace them with their code. */ -function replaceAndExtractEmojis(text: string, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE, lang = CONST.LOCALES.DEFAULT): ReplacedEmoji { +function replaceAndExtractEmojis(text: string, preferredSkinTone: number = CONST.EMOJI_DEFAULT_SKIN_TONE, lang: Locale = CONST.LOCALES.DEFAULT): ReplacedEmoji { const {text: convertedText = '', emojis = [], cursorPosition} = replaceEmojis(text, preferredSkinTone, lang); return { diff --git a/src/libs/actions/InputFocus/index.desktop.ts b/src/libs/actions/InputFocus/index.desktop.ts index 86a562f0531e..2a8fe1b9fd01 100644 --- a/src/libs/actions/InputFocus/index.desktop.ts +++ b/src/libs/actions/InputFocus/index.desktop.ts @@ -1,13 +1,14 @@ import Onyx from 'react-native-onyx'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {Modal} from '@src/types/onyx'; function inputFocusChange(focus: boolean) { Onyx.set(ONYXKEYS.INPUT_FOCUSED, focus); } let refSave: HTMLElement | undefined; -function composerFocusKeepFocusOn(ref: HTMLElement, isFocused: boolean, modal: {willAlertModalBecomeVisible: boolean; isVisible: boolean}, onyxFocused: boolean) { +function composerFocusKeepFocusOn(ref: HTMLElement, isFocused: boolean, modal: Modal, onyxFocused: boolean) { if (isFocused && !onyxFocused) { inputFocusChange(true); ref.focus(); diff --git a/src/libs/actions/InputFocus/index.ts b/src/libs/actions/InputFocus/index.ts index 1840b0625626..6d8706ebdd0e 100644 --- a/src/libs/actions/InputFocus/index.ts +++ b/src/libs/actions/InputFocus/index.ts @@ -1,5 +1,9 @@ -function inputFocusChange() {} -function composerFocusKeepFocusOn() {} -const callback = () => {}; +import type {Modal} from '@src/types/onyx'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function inputFocusChange(focus: boolean) {} +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function composerFocusKeepFocusOn(ref: HTMLElement, isFocused: boolean, modal: Modal, onyxFocused: boolean) {} +const callback = (method: () => void) => method(); export {composerFocusKeepFocusOn, inputFocusChange, callback}; diff --git a/src/libs/actions/InputFocus/index.website.ts b/src/libs/actions/InputFocus/index.website.ts index 8e41e06d7401..541254ac0cda 100644 --- a/src/libs/actions/InputFocus/index.website.ts +++ b/src/libs/actions/InputFocus/index.website.ts @@ -2,13 +2,14 @@ import Onyx from 'react-native-onyx'; import * as Browser from '@libs/Browser'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {Modal} from '@src/types/onyx'; function inputFocusChange(focus: boolean) { Onyx.set(ONYXKEYS.INPUT_FOCUSED, focus); } let refSave: HTMLElement | undefined; -function composerFocusKeepFocusOn(ref: HTMLElement, isFocused: boolean, modal: {willAlertModalBecomeVisible: boolean; isVisible: boolean}, onyxFocused: boolean) { +function composerFocusKeepFocusOn(ref: HTMLElement, isFocused: boolean, modal: Modal, onyxFocused: boolean) { if (isFocused && !onyxFocused) { inputFocusChange(true); ref.focus(); diff --git a/src/pages/home/report/ReportActionItemMessageEdit.js b/src/pages/home/report/ReportActionItemMessageEdit.tsx similarity index 75% rename from src/pages/home/report/ReportActionItemMessageEdit.js rename to src/pages/home/report/ReportActionItemMessageEdit.tsx index dbd3262f30d5..5934c4c333cb 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.js +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -1,17 +1,17 @@ import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import Str from 'expensify-common/lib/str'; -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import lodashDebounce from 'lodash/debounce'; +import type {ForwardedRef} from 'react'; +import React, {forwardRef, useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {InteractionManager, Keyboard, View} from 'react-native'; -import _ from 'underscore'; +import type {NativeSyntheticEvent, TextInput, TextInputFocusEventData, TextInputKeyPressEventData} from 'react-native'; +import type {Emoji} from '@assets/emojis/types'; import Composer from '@components/Composer'; import EmojiPickerButton from '@components/EmojiPicker/EmojiPickerButton'; import ExceededCommentLength from '@components/ExceededCommentLength'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; -import refPropTypes from '@components/refPropTypes'; import Tooltip from '@components/Tooltip'; import useHandleExceedMaxCommentLength from '@hooks/useHandleExceedMaxCommentLength'; import useKeyboardState from '@hooks/useKeyboardState'; @@ -30,48 +30,37 @@ import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManag import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; import setShouldShowComposeInputKeyboardAware from '@libs/setShouldShowComposeInputKeyboardAware'; -import reportPropTypes from '@pages/reportPropTypes'; import * as EmojiPickerAction from '@userActions/EmojiPickerAction'; import * as InputFocus from '@userActions/InputFocus'; import * as Report from '@userActions/Report'; import * as User from '@userActions/User'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type * as OnyxTypes from '@src/types/onyx'; import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu'; -import reportActionPropTypes from './reportActionPropTypes'; -const propTypes = { +type ReportActionItemMessageEditProps = { /** All the data of the action */ - action: PropTypes.shape(reportActionPropTypes).isRequired, + action: OnyxTypes.ReportAction; /** Draft message */ - draftMessage: PropTypes.string.isRequired, + draftMessage: string; /** ReportID that holds the comment we're editing */ - reportID: PropTypes.string.isRequired, + reportID: string; /** Position index of the report action in the overall report FlatList view */ - index: PropTypes.number.isRequired, - - /** A ref to forward to the text input */ - forwardedRef: refPropTypes, + index: number; /** The report currently being looked at */ // eslint-disable-next-line react/no-unused-prop-types - report: reportPropTypes, + report?: OnyxTypes.Report; /** Whether or not the emoji picker is disabled */ - shouldDisableEmojiPicker: PropTypes.bool, + shouldDisableEmojiPicker?: boolean; /** Stores user's preferred skin tone */ - preferredSkinTone: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), -}; - -const defaultProps = { - forwardedRef: () => {}, - report: {}, - shouldDisableEmojiPicker: false, - preferredSkinTone: CONST.EMOJI_DEFAULT_SKIN_TONE, + preferredSkinTone?: number; }; // native ids @@ -80,7 +69,10 @@ const messageEditInput = 'messageEditInput'; const isMobileSafari = Browser.isMobileSafari(); -function ReportActionItemMessageEdit(props) { +function ReportActionItemMessageEdit( + {action, draftMessage, reportID, index, shouldDisableEmojiPicker = false, preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE}: ReportActionItemMessageEditProps, + forwardedRef: ForwardedRef, +) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -90,13 +82,13 @@ function ReportActionItemMessageEdit(props) { const {isSmallScreenWidth} = useWindowDimensions(); const getInitialDraft = () => { - if (props.draftMessage === props.action.message[0].html) { + if (draftMessage === action?.message?.[0].html) { // We only convert the report action message to markdown if the draft message is unchanged. const parser = new ExpensiMark(); - return parser.htmlToMarkdown(props.draftMessage).trim(); + return parser.htmlToMarkdown(draftMessage).trim(); } // We need to decode saved draft message because it's escaped before saving. - return Str.htmlDecode(props.draftMessage); + return Str.htmlDecode(draftMessage); }; const getInitialSelection = () => { @@ -107,7 +99,7 @@ function ReportActionItemMessageEdit(props) { const length = getInitialDraft().length; return {start: length, end: length}; }; - const emojisPresentBefore = useRef([]); + const emojisPresentBefore = useRef([]); const [draft, setDraft] = useState(() => { const initialDraft = getInitialDraft(); if (initialDraft) { @@ -115,23 +107,29 @@ function ReportActionItemMessageEdit(props) { } return initialDraft; }); - const [selection, setSelection] = useState(getInitialSelection); - const [isFocused, setIsFocused] = useState(false); + const [selection, setSelection] = useState<{ + start: number; + end: number; + }>(getInitialSelection); + const [isFocused, setIsFocused] = useState(false); const {hasExceededMaxCommentLength, validateCommentMaxLength} = useHandleExceedMaxCommentLength(); - const [modal, setModal] = useState(false); - const [onyxFocused, setOnyxFocused] = useState(false); + const [modal, setModal] = useState({ + willAlertModalBecomeVisible: false, + isVisible: false, + }); + const [onyxFocused, setOnyxFocused] = useState(false); - const textInputRef = useRef(null); - const isFocusedRef = useRef(false); - const insertedEmojis = useRef([]); + const textInputRef = useRef<(HTMLTextAreaElement & TextInput) | null>(null); + const isFocusedRef = useRef(false); + const insertedEmojis = useRef([]); const draftRef = useRef(draft); useEffect(() => { - if (ReportActionsUtils.isDeletedAction(props.action) || props.draftMessage === props.action.message[0].html) { + if (ReportActionsUtils.isDeletedAction(action) || (action.message && draftMessage === action.message[0].html)) { return; } - setDraft(Str.htmlDecode(props.draftMessage)); - }, [props.draftMessage, props.action]); + setDraft(Str.htmlDecode(draftMessage)); + }, [draftMessage, action]); useEffect(() => { // required for keeping last state of isFocused variable @@ -139,14 +137,14 @@ function ReportActionItemMessageEdit(props) { }, [isFocused]); useEffect(() => { - InputFocus.composerFocusKeepFocusOn(textInputRef.current, isFocused, modal, onyxFocused); + InputFocus.composerFocusKeepFocusOn(textInputRef.current as HTMLElement, isFocused, modal, onyxFocused); }, [isFocused, modal, onyxFocused]); useEffect(() => { const unsubscribeOnyxModal = onyxSubscribe({ key: ONYXKEYS.MODAL, callback: (modalArg) => { - if (_.isNull(modalArg)) { + if (modalArg === null) { return; } setModal(modalArg); @@ -156,7 +154,7 @@ function ReportActionItemMessageEdit(props) { const unsubscribeOnyxFocused = onyxSubscribe({ key: ONYXKEYS.INPUT_FOCUSED, callback: (modalArg) => { - if (_.isNull(modalArg)) { + if (modalArg === null) { return; } setOnyxFocused(modalArg); @@ -170,8 +168,8 @@ function ReportActionItemMessageEdit(props) { // We consider the report action active if it's focused, its emoji picker is open or its context menu is open const isActive = useCallback( - () => isFocusedRef.current || EmojiPickerAction.isActive(props.action.reportActionID) || ReportActionContextMenu.isActiveReportAction(props.action.reportActionID), - [props.action.reportActionID], + () => isFocusedRef.current || EmojiPickerAction.isActive(action.reportActionID) || ReportActionContextMenu.isActiveReportAction(action.reportActionID), + [action.reportActionID], ); useEffect(() => { @@ -188,7 +186,9 @@ function ReportActionItemMessageEdit(props) { }); // Scroll content of textInputRef to bottom - textInputRef.current.scrollTop = textInputRef.current.scrollHeight; + if (textInputRef.current) { + textInputRef.current.scrollTop = textInputRef.current.scrollHeight; + } } return () => { @@ -200,10 +200,10 @@ function ReportActionItemMessageEdit(props) { return; } - if (EmojiPickerAction.isActive(props.action.reportActionID)) { + if (EmojiPickerAction.isActive(action.reportActionID)) { EmojiPickerAction.clearActive(); } - if (ReportActionContextMenu.isActiveReportAction(props.action.reportActionID)) { + if (ReportActionContextMenu.isActiveReportAction(action.reportActionID)) { ReportActionContextMenu.clearActiveReportAction(); } @@ -212,7 +212,7 @@ function ReportActionItemMessageEdit(props) { setShouldShowComposeInputKeyboardAware(true); }; // eslint-disable-next-line react-hooks/exhaustive-deps -- this cleanup needs to be called only on unmount - }, [props.action.reportActionID]); + }, [action.reportActionID]); /** * Save the draft of the comment. This debounced so that we're not ceaselessly saving your edit. Saving the draft @@ -221,10 +221,10 @@ function ReportActionItemMessageEdit(props) { */ const debouncedSaveDraft = useMemo( () => - _.debounce((newDraft) => { - Report.saveReportActionDraft(props.reportID, props.action, newDraft); + lodashDebounce((newDraft: string) => { + Report.saveReportActionDraft(reportID, action, newDraft); }, 1000), - [props.reportID, props.action], + [reportID, action], ); /** @@ -233,7 +233,7 @@ function ReportActionItemMessageEdit(props) { */ const debouncedUpdateFrequentlyUsedEmojis = useMemo( () => - _.debounce(() => { + lodashDebounce(() => { User.updateFrequentlyUsedEmojis(EmojiUtils.getFrequentlyUsedEmojis(insertedEmojis.current)); insertedEmojis.current = []; }, 1000), @@ -246,12 +246,12 @@ function ReportActionItemMessageEdit(props) { * @param {String} newDraftInput */ const updateDraft = useCallback( - (newDraftInput) => { - const {text: newDraft, emojis, cursorPosition} = EmojiUtils.replaceAndExtractEmojis(newDraftInput, props.preferredSkinTone, preferredLocale); + (newDraftInput: string) => { + const {text: newDraft, emojis, cursorPosition} = EmojiUtils.replaceAndExtractEmojis(newDraftInput, preferredSkinTone, preferredLocale); - if (!_.isEmpty(emojis)) { + if (emojis?.length > 0) { const newEmojis = EmojiUtils.getAddedEmojis(emojis, emojisPresentBefore.current); - if (!_.isEmpty(newEmojis)) { + if (newEmojis?.length > 0) { insertedEmojis.current = [...insertedEmojis.current, ...newEmojis]; debouncedUpdateFrequentlyUsedEmojis(); } @@ -261,7 +261,7 @@ function ReportActionItemMessageEdit(props) { setDraft(newDraft); if (newDraftInput !== newDraft) { - const position = Math.max(selection.end + (newDraft.length - draftRef.current.length), cursorPosition || 0); + const position = Math.max(selection.end + (newDraft.length - draftRef.current.length), cursorPosition ?? 0); setSelection({ start: position, end: position, @@ -271,22 +271,22 @@ function ReportActionItemMessageEdit(props) { draftRef.current = newDraft; // We want to escape the draft message to differentiate the HTML from the report action and the HTML the user drafted. - debouncedSaveDraft(_.escape(newDraft)); + debouncedSaveDraft(newDraft); }, - [debouncedSaveDraft, debouncedUpdateFrequentlyUsedEmojis, props.preferredSkinTone, preferredLocale, selection.end], + [debouncedSaveDraft, debouncedUpdateFrequentlyUsedEmojis, preferredSkinTone, preferredLocale, selection.end], ); useEffect(() => { updateDraft(draft); // eslint-disable-next-line react-hooks/exhaustive-deps -- run this only when language is changed - }, [props.action.reportActionID, preferredLocale]); + }, [action.reportActionID, preferredLocale]); /** * Delete the draft of the comment being edited. This will take the comment out of "edit mode" with the old content. */ const deleteDraft = useCallback(() => { debouncedSaveDraft.cancel(); - Report.deleteReportActionDraft(props.reportID, props.action); + Report.deleteReportActionDraft(reportID, action); if (isActive()) { ReportActionComposeFocusManager.clear(); @@ -294,13 +294,13 @@ function ReportActionItemMessageEdit(props) { } // Scroll to the last comment after editing to make sure the whole comment is clearly visible in the report. - if (props.index === 0) { + if (index === 0) { const keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', () => { - reportScrollManager.scrollToIndex(props.index, false); + reportScrollManager.scrollToIndex(index, false); keyboardDidHideListener.remove(); }); } - }, [props.action, debouncedSaveDraft, props.index, props.reportID, reportScrollManager, isActive]); + }, [action, debouncedSaveDraft, index, reportID, reportScrollManager, isActive]); /** * Save the draft of the comment to be the new comment message. This will take the comment out of "edit mode" with @@ -320,18 +320,25 @@ function ReportActionItemMessageEdit(props) { // When user tries to save the empty message, it will delete it. Prompt the user to confirm deleting. if (!trimmedNewDraft) { - textInputRef.current.blur(); - ReportActionContextMenu.showDeleteModal(props.reportID, props.action, true, deleteDraft, () => InteractionManager.runAfterInteractions(() => textInputRef.current.focus())); + textInputRef.current?.blur(); + ReportActionContextMenu.showDeleteModal( + reportID, + action, + true, + deleteDraft, + // eslint-disable-next-line @typescript-eslint/no-misused-promises + () => InteractionManager.runAfterInteractions(() => textInputRef.current?.focus()), + ); return; } - Report.editReportComment(props.reportID, props.action, trimmedNewDraft); + Report.editReportComment(reportID, action, trimmedNewDraft); deleteDraft(); - }, [props.action, debouncedSaveDraft, deleteDraft, draft, props.reportID]); + }, [action, debouncedSaveDraft, deleteDraft, draft, reportID]); /** - * @param {String} emoji + * @param emoji */ - const addEmojiToTextBox = (emoji) => { + const addEmojiToTextBox = (emoji: string) => { setSelection((prevSelection) => ({ start: prevSelection.start + emoji.length + CONST.SPACE_LENGTH, end: prevSelection.start + emoji.length + CONST.SPACE_LENGTH, @@ -345,14 +352,15 @@ function ReportActionItemMessageEdit(props) { * @param {Event} e */ const triggerSaveOrCancel = useCallback( - (e) => { + (e: NativeSyntheticEvent | KeyboardEvent) => { if (!e || ComposerUtils.canSkipTriggerHotkeys(isSmallScreenWidth, isKeyboardShown)) { return; } - if (e.key === CONST.KEYBOARD_SHORTCUTS.ENTER.shortcutKey && !e.shiftKey) { + const keyEvent = e as KeyboardEvent; + if (keyEvent.key === CONST.KEYBOARD_SHORTCUTS.ENTER.shortcutKey && !keyEvent.shiftKey) { e.preventDefault(); publishDraft(); - } else if (e.key === CONST.KEYBOARD_SHORTCUTS.ESCAPE.shortcutKey) { + } else if (keyEvent.key === CONST.KEYBOARD_SHORTCUTS.ESCAPE.shortcutKey) { e.preventDefault(); deleteDraft(); } @@ -404,11 +412,14 @@ function ReportActionItemMessageEdit(props) { { - ReportActionComposeFocusManager.editComposerRef.current = el; + ref={(el: TextInput & HTMLTextAreaElement) => { textInputRef.current = el; - // eslint-disable-next-line no-param-reassign - props.forwardedRef.current = el; + if (typeof forwardedRef === 'function') { + forwardedRef(el); + } else if (forwardedRef) { + // eslint-disable-next-line no-param-reassign + forwardedRef.current = el; + } }} id={messageEditInput} onChangeText={updateDraft} // Debounced saveDraftComment @@ -418,21 +429,22 @@ function ReportActionItemMessageEdit(props) { style={[styles.textInputCompose, styles.flex1, styles.bgTransparent]} onFocus={() => { setIsFocused(true); - reportScrollManager.scrollToIndex(props.index, true); + reportScrollManager.scrollToIndex(index, true); setShouldShowComposeInputKeyboardAware(false); // Clear active report action when another action gets focused - if (!EmojiPickerAction.isActive(props.action.reportActionID)) { + if (!EmojiPickerAction.isActive(action.reportActionID)) { EmojiPickerAction.clearActive(); } - if (!ReportActionContextMenu.isActiveReportAction(props.action.reportActionID)) { + if (!ReportActionContextMenu.isActiveReportAction(action.reportActionID)) { ReportActionContextMenu.clearActiveReportAction(); } }} - onBlur={(event) => { + onBlur={(event: NativeSyntheticEvent) => { setIsFocused(false); - const relatedTargetId = lodashGet(event, 'nativeEvent.relatedTarget.id'); - if (_.contains([messageEditInput, emojiButtonID], relatedTargetId)) { + // @ts-expect-error TODO: TextInputFocusEventData doesn't contain relatedTarget. + const relatedTargetId = event.nativeEvent?.relatedTarget?.id; + if (relatedTargetId && [messageEditInput, emojiButtonID].includes(relatedTargetId)) { return; } setShouldShowComposeInputKeyboardAware(true); @@ -443,11 +455,11 @@ function ReportActionItemMessageEdit(props) { focus(true)} onEmojiSelected={addEmojiToTextBox} id={emojiButtonID} - emojiPickerID={props.action.reportActionID} + emojiPickerID={action.reportActionID} /> @@ -478,18 +490,6 @@ function ReportActionItemMessageEdit(props) { ); } -ReportActionItemMessageEdit.propTypes = propTypes; -ReportActionItemMessageEdit.defaultProps = defaultProps; ReportActionItemMessageEdit.displayName = 'ReportActionItemMessageEdit'; -const ReportActionItemMessageEditWithRef = React.forwardRef((props, ref) => ( - -)); - -ReportActionItemMessageEditWithRef.displayName = 'ReportActionItemMessageEditWithRef'; - -export default ReportActionItemMessageEditWithRef; +export default forwardRef(ReportActionItemMessageEdit);