Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ function ComposerWithSuggestions({
const composerRef = useRef<ComposerRef | null>(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;
Expand Down Expand Up @@ -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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Route scroll requests to the money-request action list

When an expense report takes the shouldDisplayMoneyRequestActionsList branch in MoneyRequestReportView, this shared composer still creates the request, but only ReportActionsList consumes it; MoneyRequestReportActionsList never reads or clears the request. If the newest editable comment is outside that unified FlashList's render window, pressing ArrowUp therefore still leaves the inline editor unmounted and unfocused. Handle the request in both list implementations or place the handling in their shared list infrastructure.

Useful? React with 👍 / 👎.

}
}
// Flag emojis like "Wales" have several code points. Default backspace key action does not remove such flag emojis completely.
Expand Down Expand Up @@ -698,6 +702,7 @@ function ComposerWithSuggestions({
reportActions,
updateComment,
setCurrentEditMessageSelection,
requestScrollToEditingAction,
],
);

Expand Down
13 changes: 13 additions & 0 deletions src/pages/inbox/report/ReportActionEditMessageContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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<ReportActionEditMessageContextValue>({
Expand All @@ -55,13 +61,16 @@ const ReportActionEditMessageContext = createContext<ReportActionEditMessageCont
editingReportAction: null,
editingMessage: null,
currentEditMessageSelection: null,
pendingScrollToEditingReportActionID: null,
});

const ReportActionEditMessageActionsContext = createContext<ReportActionEditMessageContextActions>({
setEditingMessage: noop,
setCurrentEditMessageSelection: noop,
submitEdit: noop,
stopEditing: noop,
requestScrollToEditingAction: noop,
clearPendingScrollToEditingAction: noop,
});

type ReportActionEditMessageContextProviderProps = {
Expand All @@ -85,6 +94,7 @@ function ReportActionEditMessageContextProvider({reportID, effectiveTransactionT
const [prevEditingReportActionID, setPrevEditingReportActionID] = useState<string | null>(null);
const [editingMessage, setEditingMessage] = useState<string | null>(null);
const [currentEditMessageSelection, setCurrentEditMessageSelectionState] = useState<TextSelection | null>(null);
const [pendingScrollToEditingReportActionID, setPendingScrollToEditingReportActionID] = useState<string | null>(null);

let editingReportID: string | null = null;
let editingReportActionID: string | null = null;
Expand Down Expand Up @@ -152,13 +162,16 @@ function ReportActionEditMessageContextProvider({reportID, effectiveTransactionT
editingReportAction,
editingMessage,
currentEditMessageSelection,
pendingScrollToEditingReportActionID,
};

const actions: ReportActionEditMessageContextActions = {
setEditingMessage,
setCurrentEditMessageSelection,
submitEdit,
stopEditing,
requestScrollToEditingAction: setPendingScrollToEditingReportActionID,
clearPendingScrollToEditingAction: () => setPendingScrollToEditingReportActionID(null),
};

return (
Expand Down
23 changes: 23 additions & 0 deletions src/pages/inbox/report/ReportActionsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -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();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid subscribing the full action list to per-keystroke edit state

While a message is being edited, setEditingMessage and setCurrentEditMessageSelection update fields in this same context on essentially every keystroke or selection change. Adding useReportActionActiveEdit() here makes the entire ReportActionsListContent rerender whenever any of those unrelated fields change, repeatedly rebuilding list props and callbacks for large histories while the user types. Put the pending scroll ID in a narrowly scoped context or event channel so the list subscribes only to scroll requests.

Useful? React with 👍 / 👎.

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;
Expand Down
Loading