diff --git a/src/pages/inbox/report/ReportActionCompose/ComposerWithSuggestions.tsx b/src/pages/inbox/report/ReportActionCompose/ComposerWithSuggestions.tsx index c970a824a33c..07d6ef4ab997 100644 --- a/src/pages/inbox/report/ReportActionCompose/ComposerWithSuggestions.tsx +++ b/src/pages/inbox/report/ReportActionCompose/ComposerWithSuggestions.tsx @@ -269,7 +269,7 @@ function ComposerWithSuggestions({ const composerRef = useRef(null); const {editingState, editingReportID, editingReportAction, effectiveDraft, currentEditMessageSelection} = useComposerEditState(); - const {setEditingMessage, setCurrentEditMessageSelection} = useReportActionActiveEditActions(); + const {setEditingMessage, setCurrentEditMessageSelection, requestScrollToEditingAction} = useReportActionActiveEditActions(); const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`); const isEditing = editingState !== CONST.REPORT_ACTION_EDIT_MESSAGE_STATE.OFF; @@ -647,6 +647,10 @@ function ComposerWithSuggestions({ if (lastReportAction) { const message = Array.isArray(lastReportAction?.message) ? (lastReportAction?.message?.at(-1) ?? null) : (lastReportAction?.message ?? null); saveReportActionDraft(reportID, lastReportAction, reportActions, Parser.htmlToMarkdown(message?.html ?? '')); + + // The action being edited can be scrolled out of the list's render window, in which case its editor never mounts and never + // takes focus. Ask the list to scroll to it so it mounts, gets focused and is visible while it's edited. + requestScrollToEditingAction(lastReportAction.reportActionID); } } // Flag emojis like "Wales" have several code points. Default backspace key action does not remove such flag emojis completely. @@ -698,6 +702,7 @@ function ComposerWithSuggestions({ reportActions, updateComment, setCurrentEditMessageSelection, + requestScrollToEditingAction, ], ); diff --git a/src/pages/inbox/report/ReportActionEditMessageContext.tsx b/src/pages/inbox/report/ReportActionEditMessageContext.tsx index fe5d56fed644..052457e421e9 100644 --- a/src/pages/inbox/report/ReportActionEditMessageContext.tsx +++ b/src/pages/inbox/report/ReportActionEditMessageContext.tsx @@ -35,6 +35,8 @@ type ReportActionEditMessageContextValue = ReportActionActiveEdit & { currentEditMessageSelection: TextSelection | null; /** The editing state */ editingState: ReportActionEditMessageState; + /** The report action ID the report actions list still has to scroll into view, if any */ + pendingScrollToEditingReportActionID: string | null; }; type ReportActionEditMessageContextActions = { @@ -46,6 +48,10 @@ type ReportActionEditMessageContextActions = { submitEdit: () => void; /** Stop the editing */ stopEditing: () => void; + /** Ask the report actions list to scroll the action that just entered edit mode into view */ + requestScrollToEditingAction: (reportActionID: string) => void; + /** Clear the pending scroll request once the list has handled it */ + clearPendingScrollToEditingAction: () => void; }; const ReportActionEditMessageContext = createContext({ @@ -55,6 +61,7 @@ const ReportActionEditMessageContext = createContext({ @@ -62,6 +69,8 @@ const ReportActionEditMessageActionsContext = createContext(null); const [editingMessage, setEditingMessage] = useState(null); const [currentEditMessageSelection, setCurrentEditMessageSelectionState] = useState(null); + const [pendingScrollToEditingReportActionID, setPendingScrollToEditingReportActionID] = useState(null); let editingReportID: string | null = null; let editingReportActionID: string | null = null; @@ -152,6 +162,7 @@ function ReportActionEditMessageContextProvider({reportID, effectiveTransactionT editingReportAction, editingMessage, currentEditMessageSelection, + pendingScrollToEditingReportActionID, }; const actions: ReportActionEditMessageContextActions = { @@ -159,6 +170,8 @@ function ReportActionEditMessageContextProvider({reportID, effectiveTransactionT setCurrentEditMessageSelection, submitEdit, stopEditing, + requestScrollToEditingAction: setPendingScrollToEditingReportActionID, + clearPendingScrollToEditingAction: () => setPendingScrollToEditingReportActionID(null), }; return ( diff --git a/src/pages/inbox/report/ReportActionsList.tsx b/src/pages/inbox/report/ReportActionsList.tsx index a733aa6ed3d3..ef2621ce11f9 100644 --- a/src/pages/inbox/report/ReportActionsList.tsx +++ b/src/pages/inbox/report/ReportActionsList.tsx @@ -9,6 +9,7 @@ import useMarkAsRead from '@hooks/useMarkAsRead'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; import useReportActionsScroll from '@hooks/useReportActionsScroll'; +import useReportScrollManager from '@hooks/useReportScrollManager'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import useUnreadMarker from '@hooks/useUnreadMarker'; @@ -64,6 +65,7 @@ import {isTrackIntentUserSelector} from '@selectors/Onboarding'; import React, {useEffect, useRef, useState} from 'react'; import FloatingMessageCounter from './FloatingMessageCounter'; +import {useReportActionActiveEdit, useReportActionActiveEditActions} from './ReportActionEditMessageContext'; import ReportActionIndexContext from './ReportActionIndexContext'; import {useReportActionsListActions, useReportActionsListState} from './ReportActionsListContext'; import ReportActionsListHeader from './ReportActionsListHeader'; @@ -258,6 +260,27 @@ function ReportActionsListContent({reportID, conciergeChat, onLayout}: ReportAct revealDraftFromReportAction(persistedDraftReportAction); }, [draftReportAction, persistedDraftReportAction, revealDraftFromReportAction]); + // A message put into edit mode from the composer (ArrowUp) can sit outside the list's render window, so its editor never mounts and never + // takes focus. Scroll to it here, where the rendered indexes are known, so the row mounts and the message stays visible while it's edited. + const {pendingScrollToEditingReportActionID} = useReportActionActiveEdit(); + const {clearPendingScrollToEditingAction} = useReportActionActiveEditActions(); + const reportScrollManager = useReportScrollManager(); + + useEffect(() => { + if (!pendingScrollToEditingReportActionID) { + return; + } + + clearPendingScrollToEditingAction(); + + const editingReportActionIndex = renderedVisibleReportActions.findIndex((action) => action.reportActionID === pendingScrollToEditingReportActionID); + if (editingReportActionIndex < 0) { + return; + } + + reportScrollManager.scrollToIndex(editingReportActionIndex); + }, [clearPendingScrollToEditingAction, pendingScrollToEditingReportActionID, renderedVisibleReportActions, reportScrollManager]); + // Find the index of the action badge target in the rendered actions list (which is what the FlatList uses as data) const actionBadgeTargetID = reportAttributes?.actionTargetReportActionID; const actionBadgeTargetIndex = actionBadgeTargetID ? renderedVisibleReportActions.findIndex((action) => action.reportActionID === actionBadgeTargetID) : -1;