From c4014224362bbe8a7c216fcf528daa03cd2b4b10 Mon Sep 17 00:00:00 2001 From: KJ21-ENG Date: Tue, 19 May 2026 03:34:30 +0530 Subject: [PATCH 1/5] Support modified-click opening rows in new tabs --- .../SearchList/ListItem/ActionCell/index.tsx | 3 +- .../ListItem/ReportListItemHeader.tsx | 9 ++-- .../ListItem/TransactionGroupListExpanded.tsx | 54 ++++++++++++++----- .../ListItem/TransactionGroupListItem.tsx | 7 +-- .../TransactionListItemNarrow.tsx | 2 +- .../TransactionListItemWide.tsx | 2 +- .../ListItem/TransactionListItem/index.tsx | 8 +-- .../ListItem/TransactionListItem/types.ts | 7 +-- src/components/Search/SearchList/index.tsx | 3 +- src/components/Search/index.tsx | 44 ++++++++++++--- .../SelectionList/ListItem/BaseListItem.tsx | 2 +- .../SelectionList/ListItem/types.ts | 3 +- .../TransactionItemRowWide.tsx | 2 +- src/components/TransactionItemRow/types.ts | 7 +-- .../openInternalRouteInNewTab/index.native.ts | 28 ++++++++++ .../openInternalRouteInNewTab/index.ts | 46 ++++++++++++++++ src/libs/SearchUIUtils.ts | 24 ++++----- src/pages/workspace/WorkspacesListPage.tsx | 14 ++--- src/pages/workspace/WorkspacesListRow.tsx | 3 +- 19 files changed, 204 insertions(+), 64 deletions(-) create mode 100644 src/libs/Navigation/helpers/openInternalRouteInNewTab/index.native.ts create mode 100644 src/libs/Navigation/helpers/openInternalRouteInNewTab/index.ts diff --git a/src/components/Search/SearchList/ListItem/ActionCell/index.tsx b/src/components/Search/SearchList/ListItem/ActionCell/index.tsx index 3aa1772fba18..97c1fe4ceda5 100644 --- a/src/components/Search/SearchList/ListItem/ActionCell/index.tsx +++ b/src/components/Search/SearchList/ListItem/ActionCell/index.tsx @@ -3,6 +3,7 @@ import Button from '@components/Button'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; +import type {ModifiedMouseEvent} from '@libs/Navigation/helpers/openInternalRouteInNewTab'; import CONST from '@src/CONST'; import type {SearchTransactionAction} from '@src/types/onyx/SearchResults'; import actionTranslationsMap from './actionTranslationsMap'; @@ -11,7 +12,7 @@ import PayActionCell from './PayActionCell'; type ActionCellProps = { action?: SearchTransactionAction; isSelected?: boolean; - onButtonPress: () => void; + onButtonPress: (event?: ModifiedMouseEvent) => void; isChildListItem?: boolean; isLoading?: boolean; policyID?: string; diff --git a/src/components/Search/SearchList/ListItem/ReportListItemHeader.tsx b/src/components/Search/SearchList/ListItem/ReportListItemHeader.tsx index 1f94e3f3f984..5a6704a85d2a 100644 --- a/src/components/Search/SearchList/ListItem/ReportListItemHeader.tsx +++ b/src/components/Search/SearchList/ListItem/ReportListItemHeader.tsx @@ -15,6 +15,7 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; +import type {ModifiedMouseEvent} from '@libs/Navigation/helpers/openInternalRouteInNewTab'; import {handleActionButtonPress} from '@userActions/Search'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -30,7 +31,7 @@ type ReportListItemHeaderProps = SearchListActionProps & report: TransactionReportGroupListItemType; /** Callback to fire when the item is pressed */ - onSelectRow: (item: TItem) => void; + onSelectRow: (item: TItem, event?: ModifiedMouseEvent) => void; /** Callback to fire when a checkbox is pressed */ onCheckboxPress?: (item: TItem) => void; @@ -74,7 +75,7 @@ type FirstRowReportHeaderProps = { canSelectMultiple: boolean | undefined; /** Callback passed as goToItem in actionCell, triggered by clicking actionButton */ - handleOnButtonPress?: () => void; + handleOnButtonPress?: (event?: ModifiedMouseEvent) => void; /** Color of the secondary avatar border, usually should match the container background */ avatarBorderColor?: ColorValue; @@ -228,11 +229,11 @@ function ReportListItemHeader({ StyleUtils.getItemBackgroundColorStyle(!!reportItem.isSelected, !!isFocused || !!isHovered, !!isDisabled, theme.activeComponentBG, theme.hoverComponentBG)?.backgroundColor ?? theme.highlightBG; - const handleOnButtonPress = () => { + const handleOnButtonPress = (event?: ModifiedMouseEvent) => { handleActionButtonPress({ hash: currentSearchHash, item: reportItem, - goToItem: () => onSelectRow(reportItem as unknown as TItem), + goToItem: () => onSelectRow(reportItem as unknown as TItem, event), snapshotReport, snapshotPolicy, policy: parentPolicy, diff --git a/src/components/Search/SearchList/ListItem/TransactionGroupListExpanded.tsx b/src/components/Search/SearchList/ListItem/TransactionGroupListExpanded.tsx index 08b8d1129f27..b18901e67b7e 100644 --- a/src/components/Search/SearchList/ListItem/TransactionGroupListExpanded.tsx +++ b/src/components/Search/SearchList/ListItem/TransactionGroupListExpanded.tsx @@ -21,6 +21,8 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import {getReportIDForTransaction} from '@libs/MoneyRequestReportUtils'; +import openInternalRouteInNewTab, {isModifiedMousePress} from '@libs/Navigation/helpers/openInternalRouteInNewTab'; +import type {ModifiedMouseEvent} from '@libs/Navigation/helpers/openInternalRouteInNewTab'; import Navigation from '@libs/Navigation/Navigation'; import {getReportAction} from '@libs/ReportActionsUtils'; import {getReportOrDraftReport} from '@libs/ReportUtils'; @@ -132,7 +134,7 @@ function TransactionGroupListExpanded({ const isActionColumnWide = transactions.some((transaction) => !!transaction.isActionColumnWide || isDeletedTransaction(transaction)); const {markReportIDAsExpense} = useWideRHPActions(); - const selectRow = onSelectRow as (item: TItem, transactionPreviewData?: TransactionPreviewData) => void; + const selectRow = onSelectRow as (item: TItem, transactionPreviewData?: TransactionPreviewData, event?: ModifiedMouseEvent) => void; const getTransactionPreviewData = (transactionItem: TransactionListItemType): TransactionPreviewData => { const parentReportAction = getReportAction(transactionItem?.reportID, transactionItem?.reportAction?.reportActionID); const parentReport = getReportOrDraftReport(transactionItem?.reportID, undefined, undefined, undefined, allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionItem?.reportID}`]); @@ -153,12 +155,29 @@ function TransactionGroupListExpanded({ }; }; - const openReportInRHP = (transactionItem: TransactionListItemType) => { + const openReportInRHP = (transactionItem: TransactionListItemType, event?: ModifiedMouseEvent) => { const backTo = Navigation.getActiveRoute(); const reportID = getReportIDForTransaction(transactionItem, transactionItem?.reportAction?.childReportID); const navigateToTransactionThread = () => { if (!transactionItem?.reportAction?.childReportID) { + if (isModifiedMousePress(event)) { + const targetReportID = createAndOpenSearchTransactionThread( + transactionItem, + introSelected, + backTo, + currentUserDetails.email ?? '', + currentUserDetails.accountID, + betas, + transactionItem?.reportAction?.childReportID, + undefined, + false, + ); + if (targetReportID) { + openInternalRouteInNewTab(ROUTES.SEARCH_REPORT.getRoute({reportID: targetReportID, backTo}), event); + } + return; + } createAndOpenSearchTransactionThread( transactionItem, introSelected, @@ -171,12 +190,16 @@ function TransactionGroupListExpanded({ return; } markReportIDAsExpense(reportID); - Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID, backTo})); + const route = ROUTES.SEARCH_REPORT.getRoute({reportID, backTo}); + if (openInternalRouteInNewTab(route, event)) { + return; + } + Navigation.navigate(route); }; // The arrow navigation in RHP is only allowed for group-by:reports if (!isExpenseReportType) { - selectRow(transactionItem as unknown as TItem, getTransactionPreviewData(transactionItem)); + selectRow(transactionItem as unknown as TItem, getTransactionPreviewData(transactionItem), event); return; } @@ -186,10 +209,13 @@ function TransactionGroupListExpanded({ // When opening the transaction thread in RHP we need to find every other ID for the rest of transactions // to display prev/next arrows in RHP for navigation - setActiveTransactionIDs(siblingTransactionIDs).then(() => { - // If we're trying to open a transaction without a transaction thread, let's create the thread and navigate the user + if (isModifiedMousePress(event)) { + setActiveTransactionIDs(siblingTransactionIDs); navigateToTransactionThread(); - }); + return; + } + + setActiveTransactionIDs(siblingTransactionIDs).then(navigateToTransactionThread); }; const onShowMoreButtonPress = () => { @@ -218,20 +244,20 @@ function TransactionGroupListExpanded({ ); } - const handleOnPress = (transaction: TransactionListItemType) => { + const handleOnPress = (transaction: TransactionListItemType, event?: ModifiedMouseEvent) => { if (isMobileSelectionModeEnabled) { onSelectionButtonPress?.(transaction as unknown as TItem); return; } - openReportInRHP(transaction); + openReportInRHP(transaction, event); }; - const handleButtonPress = (transaction: TransactionListItemType) => { + const handleButtonPress = (transaction: TransactionListItemType, event?: ModifiedMouseEvent) => { if (transaction.action === CONST.SEARCH.ACTION_TYPES.UNDELETE) { onUndelete?.(transaction); return; } - openReportInRHP(transaction); + openReportInRHP(transaction, event); }; const dataColumns = currentColumns.filter((column) => !column.startsWith(CONST.SEARCH.GROUP_COLUMN_PREFIX)) ?? []; @@ -273,7 +299,7 @@ function TransactionGroupListExpanded({ key={transaction.transactionID} > handleOnPress(transaction)} + onPress={isDeletedOrPendingDelete && !canSelectMultiple ? undefined : (event) => handleOnPress(transaction, event)} disabled={isTransactionPendingDelete(transaction) && !transaction.isSelected} onLongPress={() => onLongPress?.(transaction)} accessibilityRole={CONST.ROLE.BUTTON} @@ -310,12 +336,12 @@ function TransactionGroupListExpanded({ checkboxSentryLabel={CONST.SENTRY_LABEL.SEARCH.EXPANDED_TRANSACTION_ROW_CHECKBOX} onCheckboxPress={() => onSelectionButtonPress?.(transaction as unknown as TItem)} columns={currentColumns} - onButtonPress={() => handleButtonPress(transaction)} + onButtonPress={(event) => handleButtonPress(transaction, event)} style={[styles.noBorderRadius, isLargeScreenWidth ? [styles.p3, styles.pv2, styles.tableRowHeight] : styles.p4, styles.flex1]} isReportItemChild isInSingleTransactionReport={isInSingleTransactionReport} shouldShowBottomBorder={shouldShowBottomBorder} - onArrowRightPress={isDeletedOrPendingDelete ? undefined : () => openReportInRHP(transaction)} + onArrowRightPress={isDeletedOrPendingDelete ? undefined : (event) => openReportInRHP(transaction, event)} shouldShowArrowRightOnNarrowLayout reportActions={exportedReportActions} isAttendeesEnabledForMovingPolicy={isAttendeesEnabledForMovingPolicy} diff --git a/src/components/Search/SearchList/ListItem/TransactionGroupListItem.tsx b/src/components/Search/SearchList/ListItem/TransactionGroupListItem.tsx index 7689904aaecf..6d1b9035cb10 100644 --- a/src/components/Search/SearchList/ListItem/TransactionGroupListItem.tsx +++ b/src/components/Search/SearchList/ListItem/TransactionGroupListItem.tsx @@ -27,6 +27,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import {search} from '@libs/actions/Search'; import type {TransactionPreviewData} from '@libs/actions/Search'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; +import type {ModifiedMouseEvent} from '@libs/Navigation/helpers/openInternalRouteInNewTab'; import {getSections} from '@libs/SearchUIUtils'; import {mergeProhibitedViolations, shouldShowViolation} from '@libs/TransactionUtils'; import variables from '@styles/variables'; @@ -276,9 +277,9 @@ function TransactionGroupListItem({ }); }; - const onPress = () => { + const onPress = (event?: ModifiedMouseEvent) => { if (isExpenseReportType || transactions.length === 0) { - onSelectRow(item, transactionPreviewData); + onSelectRow(item, transactionPreviewData, event); } if (!isExpenseReportType) { handleToggle(); @@ -446,7 +447,7 @@ function TransactionGroupListItem({ return ( onSelectRow(listItem, transactionPreviewData)} + onSelectRow={(listItem, event) => onSelectRow(listItem, transactionPreviewData, event)} onCheckboxPress={handleSelectionButtonPress} isDisabled={isDisabled} isFocused={isFocused} diff --git a/src/components/Search/SearchList/ListItem/TransactionListItem/TransactionListItemNarrow.tsx b/src/components/Search/SearchList/ListItem/TransactionListItem/TransactionListItemNarrow.tsx index fd61c97ce048..513ba1cc9705 100644 --- a/src/components/Search/SearchList/ListItem/TransactionListItem/TransactionListItemNarrow.tsx +++ b/src/components/Search/SearchList/ListItem/TransactionListItem/TransactionListItemNarrow.tsx @@ -138,7 +138,7 @@ function TransactionListItemNarrow({ checkboxSentryLabel={CONST.SENTRY_LABEL.SEARCH.TRANSACTION_LIST_ITEM_CHECKBOX} style={[styles.p3, styles.pv2, styles.p0, styles.pt3, isLastItem ? styles.tableBottomRadius : styles.noBorderRadius]} violations={transactionViolations} - onArrowRightPress={isDeletedTransaction ? undefined : () => onSelectRow(item, transactionPreviewData)} + onArrowRightPress={isDeletedTransaction ? undefined : (event) => onSelectRow(item, transactionPreviewData, event)} isHover={false} nonPersonalAndWorkspaceCards={nonPersonalAndWorkspaceCards} reportActions={exportedReportActions} diff --git a/src/components/Search/SearchList/ListItem/TransactionListItem/TransactionListItemWide.tsx b/src/components/Search/SearchList/ListItem/TransactionListItem/TransactionListItemWide.tsx index f9139338a74d..7b0432ad4113 100644 --- a/src/components/Search/SearchList/ListItem/TransactionListItem/TransactionListItemWide.tsx +++ b/src/components/Search/SearchList/ListItem/TransactionListItem/TransactionListItemWide.tsx @@ -136,7 +136,7 @@ function TransactionListItemWide({ checkboxSentryLabel={CONST.SENTRY_LABEL.SEARCH.TRANSACTION_LIST_ITEM_CHECKBOX} style={[styles.p3, styles.pv2, isLastItem ? styles.tableBottomRadius : styles.noBorderRadius]} violations={transactionViolations} - onArrowRightPress={isDeletedTransaction ? undefined : () => onSelectRow(item, transactionPreviewData)} + onArrowRightPress={isDeletedTransaction ? undefined : (event) => onSelectRow(item, transactionPreviewData, event)} isHover={hovered} nonPersonalAndWorkspaceCards={nonPersonalAndWorkspaceCards} reportActions={exportedReportActions} diff --git a/src/components/Search/SearchList/ListItem/TransactionListItem/index.tsx b/src/components/Search/SearchList/ListItem/TransactionListItem/index.tsx index b90486d6b98e..b9b243cb2cef 100644 --- a/src/components/Search/SearchList/ListItem/TransactionListItem/index.tsx +++ b/src/components/Search/SearchList/ListItem/TransactionListItem/index.tsx @@ -162,7 +162,7 @@ function TransactionListItem({ linkedReportAction: transactionItem.reportAction, }); - const handleOnPress = () => { + const handleOnPress = (event?: Parameters[2]) => { // Consume the tap that dismissed an editing cell — a second tap will open the row. // We check the ref rather than isEditingCell because blur fires before onPress and resets the state. if (wasEditingOnMouseDownRef.current) { @@ -176,7 +176,7 @@ function TransactionListItem({ if (isDeletedTransaction && !canSelectMultiple) { return; } - onSelectRow(item, transactionPreviewData); + onSelectRow(item, transactionPreviewData, event); }; const handleOnMouseDown = (e?: React.MouseEvent) => { @@ -190,11 +190,11 @@ function TransactionListItem({ const handleOnHoverIn = () => setShouldDisableHoverStyle(false); - const handleActionButtonPress = () => { + const handleActionButtonPress = (event?: Parameters[2]) => { handleActionButtonPressUtil({ hash: currentSearchHash, item: transactionItem, - goToItem: () => onSelectRow(item, transactionPreviewData), + goToItem: () => onSelectRow(item, transactionPreviewData, event), snapshotReport, snapshotPolicy, policy: parentPolicy, diff --git a/src/components/Search/SearchList/ListItem/TransactionListItem/types.ts b/src/components/Search/SearchList/ListItem/TransactionListItem/types.ts index e6b89b457e2f..bf9a161e6be2 100644 --- a/src/components/Search/SearchList/ListItem/TransactionListItem/types.ts +++ b/src/components/Search/SearchList/ListItem/TransactionListItem/types.ts @@ -4,11 +4,12 @@ import type {SearchColumnType} from '@components/Search/types'; import type {ListItemFocusEventHandler} from '@components/SelectionList/ListItem/types'; import type {ListItem} from '@components/SelectionList/types'; import type {TransactionPreviewData} from '@libs/actions/Search'; +import type {ModifiedMouseEvent} from '@libs/Navigation/helpers/openInternalRouteInNewTab'; import type {CardList, ReportAction, TransactionViolation} from '@src/types/onyx'; type TransactionListItemInlineEditProps = { shouldDisableHoverStyle: boolean; - onPressRow: () => void; + onPressRow: (event?: ModifiedMouseEvent) => void; onMouseDownRow: (e?: MouseEvent) => void; onHoverInRow: () => void; onEditDate: (newDate: string) => void; @@ -33,7 +34,7 @@ type TransactionListItemWideProps = { showTooltip: boolean; isDisabled?: boolean | null; canSelectMultiple?: boolean; - onSelectRow: (item: TItem, transactionPreviewData?: TransactionPreviewData) => void; + onSelectRow: (item: TItem, transactionPreviewData?: TransactionPreviewData, event?: ModifiedMouseEvent) => void; onCheckboxPress?: (item: TItem) => void; onFocus?: ListItemFocusEventHandler; onLongPressRow?: (item: TItem) => void; @@ -43,7 +44,7 @@ type TransactionListItemWideProps = { isActionLoading?: boolean; isLastItem?: boolean; transactionViolations: TransactionViolation[]; - handleActionButtonPress: () => void; + handleActionButtonPress: (event?: ModifiedMouseEvent) => void; transactionPreviewData: TransactionPreviewData; exportedReportActions: ReportAction[]; nonPersonalAndWorkspaceCards?: CardList; diff --git a/src/components/Search/SearchList/index.tsx b/src/components/Search/SearchList/index.tsx index c01db4ee3120..65ae99475413 100644 --- a/src/components/Search/SearchList/index.tsx +++ b/src/components/Search/SearchList/index.tsx @@ -30,6 +30,7 @@ import useWindowDimensions from '@hooks/useWindowDimensions'; import {turnOnMobileSelectionMode} from '@libs/actions/MobileSelectionMode'; import DateUtils from '@libs/DateUtils'; import navigationRef from '@libs/Navigation/navigationRef'; +import type {ModifiedMouseEvent} from '@libs/Navigation/helpers/openInternalRouteInNewTab'; import {applySelectionToItem, getTableMinWidth} from '@libs/SearchUIUtils'; import variables from '@styles/variables'; import type {TransactionPreviewData} from '@userActions/Search'; @@ -77,7 +78,7 @@ type SearchListProps = Pick, 'onScroll' | 'conten SearchTableHeader?: React.JSX.Element; /** Callback to fire when a row is pressed */ - onSelectRow: (item: SearchListItem, transactionPreviewData?: TransactionPreviewData) => void; + onSelectRow: (item: SearchListItem, transactionPreviewData?: TransactionPreviewData, event?: ModifiedMouseEvent) => void; /** Whether this is a multi-select list */ canSelectMultiple: boolean; diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index db66d401a73e..9f3112a9f339 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -34,6 +34,8 @@ import type {TransactionPreviewData} from '@libs/actions/Search'; import {setOptimisticDataForTransactionThreadPreview} from '@libs/actions/Search'; import {flushDeferredWrite, hasDeferredWrite} from '@libs/deferredLayoutWrite'; import Log from '@libs/Log'; +import openInternalRouteInNewTab, {isModifiedMousePress} from '@libs/Navigation/helpers/openInternalRouteInNewTab'; +import type {ModifiedMouseEvent} from '@libs/Navigation/helpers/openInternalRouteInNewTab'; import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute'; import type {PlatformStackNavigationProp} from '@libs/Navigation/PlatformStackNavigation/types'; import TransitionTracker from '@libs/Navigation/TransitionTracker'; @@ -1082,7 +1084,7 @@ function Search({ }; const onSelectRow = useCallback( - (item: SearchListItem, transactionPreviewData?: TransactionPreviewData) => { + (item: SearchListItem, transactionPreviewData?: TransactionPreviewData, event?: ModifiedMouseEvent) => { if (item.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE) { return; } @@ -1093,7 +1095,21 @@ function Search({ if (isTransactionItem && !item?.reportAction?.childReportID) { // If the report is unreported (self DM), we want to open the track expense thread instead of a report with an ID of 0 const shouldOpenTransactionThread = !isOneTransactionReport(item.report) || item.reportID === CONST.REPORT.UNREPORTED_REPORT_ID; - createAndOpenSearchTransactionThread(item, introSelected, backTo, email ?? '', accountID, betas, item?.reportAction?.childReportID, undefined, shouldOpenTransactionThread); + const shouldOpenTransactionThreadInNewTab = shouldOpenTransactionThread && isModifiedMousePress(event); + const targetReportID = createAndOpenSearchTransactionThread( + item, + introSelected, + backTo, + email ?? '', + accountID, + betas, + item?.reportAction?.childReportID, + undefined, + shouldOpenTransactionThread && !shouldOpenTransactionThreadInNewTab, + ); + if (shouldOpenTransactionThreadInNewTab && targetReportID) { + openInternalRouteInNewTab(ROUTES.SEARCH_REPORT.getRoute({reportID: targetReportID, backTo}), event); + } if (shouldOpenTransactionThread) { return; } @@ -1172,7 +1188,11 @@ function Search({ allowPostSearchRecount: true, }); - requestAnimationFrame(() => Navigation.navigate(ROUTES.SEARCH_MONEY_REQUEST_REPORT.getRoute({reportID, backTo}))); + const route = ROUTES.SEARCH_MONEY_REQUEST_REPORT.getRoute({reportID, backTo}); + if (openInternalRouteInNewTab(route, event)) { + return; + } + requestAnimationFrame(() => Navigation.navigate(route)); return; } @@ -1183,12 +1203,20 @@ function Search({ isCreatedTaskReportAction(reportActionItem) && (isOptimisticCreatedTaskAction || reportActionItem.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); const reportActionID = shouldSkipReportActionID ? undefined : reportActionItem.reportActionID; - Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID, reportActionID, backTo})); + const route = ROUTES.SEARCH_REPORT.getRoute({reportID, reportActionID, backTo}); + if (openInternalRouteInNewTab(route, event)) { + return; + } + Navigation.navigate(route); return; } if (isTaskListItemType(item)) { - requestAnimationFrame(() => Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID, backTo}))); + const route = ROUTES.SEARCH_REPORT.getRoute({reportID, backTo}); + if (openInternalRouteInNewTab(route, event)) { + return; + } + requestAnimationFrame(() => Navigation.navigate(route)); return; } @@ -1198,7 +1226,11 @@ function Search({ setOptimisticDataForTransactionThreadPreview(transactionItem, transactionPreviewData, transactionItem?.reportAction?.childReportID); } - requestAnimationFrame(() => Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID, backTo}))); + const route = ROUTES.SEARCH_REPORT.getRoute({reportID, backTo}); + if (openInternalRouteInNewTab(route, event)) { + return; + } + requestAnimationFrame(() => Navigation.navigate(route)); }, [ markReportIDAsExpense, diff --git a/src/components/SelectionList/ListItem/BaseListItem.tsx b/src/components/SelectionList/ListItem/BaseListItem.tsx index 523540bb783f..c507255a70e5 100644 --- a/src/components/SelectionList/ListItem/BaseListItem.tsx +++ b/src/components/SelectionList/ListItem/BaseListItem.tsx @@ -191,7 +191,7 @@ function BaseListItem({ if (shouldPreventEnterKeySubmit && e && 'key' in e && e.key === CONST.KEYBOARD_SHORTCUTS.ENTER.shortcutKey) { return; } - onSelectRow(item); + onSelectRow(item, undefined, e); }} disabled={isDisabled && !item.isSelected} interactive={item.isInteractive} diff --git a/src/components/SelectionList/ListItem/types.ts b/src/components/SelectionList/ListItem/types.ts index 115e7acfab3b..b49658df121b 100644 --- a/src/components/SelectionList/ListItem/types.ts +++ b/src/components/SelectionList/ListItem/types.ts @@ -7,6 +7,7 @@ import type {SearchRouterItem} from '@components/Search/SearchAutocompleteList'; import type {TransactionListItemType} from '@components/Search/SearchList/ListItem/types'; import type {TransactionPreviewData} from '@libs/actions/Search'; import type {ForwardedFSClassProps} from '@libs/Fullstory/types'; +import type {ModifiedMouseEvent} from '@libs/Navigation/helpers/openInternalRouteInNewTab'; import type {BrickRoad} from '@libs/WorkspacesSettingsUtils'; // eslint-disable-next-line no-restricted-imports import type CursorStyles from '@styles/utils/cursor/types'; @@ -160,7 +161,7 @@ type CommonListItemProps = { canSelectMultiple?: boolean; /** Callback to fire when the item is pressed */ - onSelectRow: (item: TItem, transactionPreviewData?: TransactionPreviewData) => void; + onSelectRow: (item: TItem, transactionPreviewData?: TransactionPreviewData, event?: ModifiedMouseEvent) => void; /** Callback to fire when an error is dismissed */ onDismissError?: (item: TItem) => void; diff --git a/src/components/TransactionItemRow/TransactionItemRowWide.tsx b/src/components/TransactionItemRow/TransactionItemRowWide.tsx index 678d5bf34c3f..4d1052dccf27 100644 --- a/src/components/TransactionItemRow/TransactionItemRowWide.tsx +++ b/src/components/TransactionItemRow/TransactionItemRowWide.tsx @@ -604,7 +604,7 @@ function TransactionItemRowWide({ {onArrowRightPress ? ( onArrowRightPress?.()} + onPress={onArrowRightPress} style={[styles.pv2, styles.justifyContentCenter, styles.alignItemsEnd]} accessibilityRole={CONST.ROLE.BUTTON} accessibilityLabel={CONST.ROLE.BUTTON} diff --git a/src/components/TransactionItemRow/types.ts b/src/components/TransactionItemRow/types.ts index 64f442cc83e8..77df4666b994 100644 --- a/src/components/TransactionItemRow/types.ts +++ b/src/components/TransactionItemRow/types.ts @@ -1,6 +1,7 @@ import type {StyleProp, ViewStyle} from 'react-native'; import type {TransactionWithOptionalHighlight} from '@components/MoneyRequestReportView/MoneyRequestReportTransactionList'; import type {SearchColumnType, TableColumnSize} from '@components/Search/types'; +import type {ModifiedMouseEvent} from '@libs/Navigation/helpers/openInternalRouteInNewTab'; import type {CardList, PersonalDetails, Policy, Report, ReportAction, TransactionViolation} from '@src/types/onyx'; import type {Attendee} from '@src/types/onyx/IOU'; import type {SearchTransactionAction} from '@src/types/onyx/SearchResults'; @@ -10,7 +11,7 @@ type TransactionWithOptionalSearchFields = TransactionWithOptionalHighlight & { action?: SearchTransactionAction; /** Function passed to the action button, triggered when the button is pressed */ - onButtonPress?: () => void; + onButtonPress?: (event?: ModifiedMouseEvent) => void; /** The personal details of the user requesting money */ from?: PersonalDetails; @@ -66,7 +67,7 @@ type TransactionItemRowProps = { onCheckboxPress?: (transactionID: string) => void; shouldShowCheckbox?: boolean; columns?: SearchColumnType[]; - onButtonPress?: () => void; + onButtonPress?: (event?: ModifiedMouseEvent) => void; style?: StyleProp; isReportItemChild?: boolean; isActionLoading?: boolean; @@ -78,7 +79,7 @@ type TransactionItemRowProps = { isDisabled?: boolean; violations?: TransactionViolation[]; shouldShowBottomBorder?: boolean; - onArrowRightPress?: () => void; + onArrowRightPress?: (event?: ModifiedMouseEvent) => void; isHover?: boolean; shouldShowArrowRightOnNarrowLayout?: boolean; reportActions?: ReportAction[]; diff --git a/src/libs/Navigation/helpers/openInternalRouteInNewTab/index.native.ts b/src/libs/Navigation/helpers/openInternalRouteInNewTab/index.native.ts new file mode 100644 index 000000000000..5adea91a6a6f --- /dev/null +++ b/src/libs/Navigation/helpers/openInternalRouteInNewTab/index.native.ts @@ -0,0 +1,28 @@ +import type {Route} from '@src/ROUTES'; + +type ModifiedMouseEvent = { + preventDefault?: () => void; + stopPropagation?: () => void; + metaKey?: boolean; + ctrlKey?: boolean; + button?: number; + key?: string; + nativeEvent?: { + metaKey?: boolean; + ctrlKey?: boolean; + button?: number; + key?: string; + }; +}; + +function isModifiedMousePress(_event?: ModifiedMouseEvent) { + return false; +} + +function openInternalRouteInNewTab(_route: Route, _event?: ModifiedMouseEvent) { + return false; +} + +export {isModifiedMousePress}; +export type {ModifiedMouseEvent}; +export default openInternalRouteInNewTab; diff --git a/src/libs/Navigation/helpers/openInternalRouteInNewTab/index.ts b/src/libs/Navigation/helpers/openInternalRouteInNewTab/index.ts new file mode 100644 index 000000000000..5fd5a595cfd3 --- /dev/null +++ b/src/libs/Navigation/helpers/openInternalRouteInNewTab/index.ts @@ -0,0 +1,46 @@ +import type {Route} from '@src/ROUTES'; + +type ModifiedMouseEvent = { + preventDefault?: () => void; + stopPropagation?: () => void; + metaKey?: boolean; + ctrlKey?: boolean; + button?: number; + key?: string; + nativeEvent?: { + metaKey?: boolean; + ctrlKey?: boolean; + button?: number; + key?: string; + }; +}; + +function getRouteURL(route: Route) { + return new URL(route.startsWith('/') ? route : `/${route}`, window.location.origin).toString(); +} + +function isModifiedMousePress(event?: ModifiedMouseEvent) { + const mouseEvent = event?.nativeEvent ?? event; + + if (!mouseEvent || 'key' in mouseEvent) { + return false; + } + + const isPrimaryButton = mouseEvent.button === undefined || mouseEvent.button === 0; + return isPrimaryButton && (!!mouseEvent.metaKey || !!mouseEvent.ctrlKey); +} + +function openInternalRouteInNewTab(route: Route, event?: ModifiedMouseEvent) { + if (!isModifiedMousePress(event) || typeof window === 'undefined') { + return false; + } + + event?.preventDefault?.(); + event?.stopPropagation?.(); + window.open(getRouteURL(route), '_blank', 'noopener,noreferrer'); + return true; +} + +export {isModifiedMousePress}; +export type {ModifiedMouseEvent}; +export default openInternalRouteInNewTab; diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index e6ac8061b246..2af667195222 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -2501,7 +2501,7 @@ function createAndOpenSearchTransactionThread( IOUTransactionID?: string, transactionPreviewData?: TransactionPreviewData, shouldNavigate = true, -) { +): string | undefined { const isFromSelfDM = item.reportID === CONST.REPORT.UNREPORTED_REPORT_ID; const isDeleted = isDeletedTransaction(item); const iouReportAction = getIOUActionForReportID(isFromSelfDM ? findSelfDMReportID() : item.reportID, item.transactionID); @@ -2540,20 +2540,18 @@ function createAndOpenSearchTransactionThread( }); } - if (shouldNavigate) { - // Navigate to transaction thread if there are multiple transactions in the report, or to the parent report if it's the only transaction - const isFromOneTransactionReport = isOneTransactionReport(item.report); - const shouldNavigateToTransactionThread = (!isFromOneTransactionReport || isFromSelfDM || isDeleted) && transactionThreadReport?.reportID !== CONST.REPORT.UNREPORTED_REPORT_ID; - // When we have an actual transaction thread (childReportID from Onyx) but the report isn't in Onyx yet - // (e.g. Search didn't return the IOU action for deleted items), use childReportID directly so we don't navigate with undefined - const targetReportID = shouldNavigateToTransactionThread - ? (transactionThreadReport?.reportID ?? (hasActualTransactionThread ? iouReportAction.childReportID : undefined)) - : item.reportID; + // Navigate to transaction thread if there are multiple transactions in the report, or to the parent report if it's the only transaction + const isFromOneTransactionReport = isOneTransactionReport(item.report); + const shouldNavigateToTransactionThread = (!isFromOneTransactionReport || isFromSelfDM || isDeleted) && transactionThreadReport?.reportID !== CONST.REPORT.UNREPORTED_REPORT_ID; + // When we have an actual transaction thread (childReportID from Onyx) but the report isn't in Onyx yet + // (e.g. Search didn't return the IOU action for deleted items), use childReportID directly so we don't navigate with undefined + const targetReportID = shouldNavigateToTransactionThread ? (transactionThreadReport?.reportID ?? (hasActualTransactionThread ? iouReportAction.childReportID : undefined)) : item.reportID; - if (targetReportID) { - Navigation.setNavigationActionToMicrotaskQueue(() => Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID: targetReportID, backTo}))); - } + if (shouldNavigate && targetReportID) { + Navigation.setNavigationActionToMicrotaskQueue(() => Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID: targetReportID, backTo}))); } + + return targetReportID; } /** diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx index f59557695e73..1cf05017c79c 100755 --- a/src/pages/workspace/WorkspacesListPage.tsx +++ b/src/pages/workspace/WorkspacesListPage.tsx @@ -49,6 +49,8 @@ import {callFunctionIfActionIsAllowed} from '@libs/actions/Session'; import {filterInactiveCards} from '@libs/CardUtils'; import {hasDomainErrors} from '@libs/DomainUtils'; import {getLatestErrorMessage} from '@libs/ErrorUtils'; +import openInternalRouteInNewTab from '@libs/Navigation/helpers/openInternalRouteInNewTab'; +import type {ModifiedMouseEvent} from '@libs/Navigation/helpers/openInternalRouteInNewTab'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import TransitionTracker from '@libs/Navigation/TransitionTracker'; @@ -93,7 +95,7 @@ type WorkspaceItem = {listItemType: 'workspace'} & ListItem & Pick & Pick & { icon: AvatarSource; - action: () => void; + action: (event?: ModifiedMouseEvent) => void; dismissError: () => void; iconType?: ValueOf; policyID?: string; @@ -508,12 +510,12 @@ function WorkspacesListPage() { ); }; - const navigateToWorkspace = (policyID: string) => { - if (shouldUseNarrowLayout) { - Navigation.navigate(ROUTES.WORKSPACE_INITIAL.getRoute(policyID)); + const navigateToWorkspace = (policyID: string, event?: ModifiedMouseEvent) => { + const route = shouldUseNarrowLayout ? ROUTES.WORKSPACE_INITIAL.getRoute(policyID) : ROUTES.WORKSPACE_OVERVIEW.getRoute(policyID); + if (openInternalRouteInNewTab(route, event)) { return; } - Navigation.navigate(ROUTES.WORKSPACE_OVERVIEW.getRoute(policyID)); + Navigation.navigate(route); }; const navigateToDomain = ({domainAccountID, isAdmin}: {domainAccountID: number; isAdmin: boolean}) => { @@ -583,7 +585,7 @@ function WorkspacesListPage() { listItemType: 'workspace', title: policy.name, icon: policy.avatarURL ? policy.avatarURL : getDefaultWorkspaceAvatar(policy.name), - action: () => navigateToWorkspace(policy.id), + action: (event) => navigateToWorkspace(policy.id, event), brickRoadIndicator, pendingAction: policy.pendingAction, errors: policy.errors, diff --git a/src/pages/workspace/WorkspacesListRow.tsx b/src/pages/workspace/WorkspacesListRow.tsx index de187a8fbea6..16699ca4babf 100644 --- a/src/pages/workspace/WorkspacesListRow.tsx +++ b/src/pages/workspace/WorkspacesListRow.tsx @@ -23,6 +23,7 @@ import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import type {ModifiedMouseEvent} from '@libs/Navigation/helpers/openInternalRouteInNewTab'; import {getDisplayNameOrDefault, getPersonalDetailsByIDs} from '@libs/PersonalDetailsUtils'; import {getUserFriendlyWorkspaceType} from '@libs/PolicyUtils'; import type {AvatarSource} from '@libs/UserAvatarUtils'; @@ -90,7 +91,7 @@ type WorkspacesListRowProps = WithCurrentUserPersonalDetailsProps & { disabled?: boolean; /** Callback when the row is pressed */ - onPress?: () => void; + onPress?: (event?: ModifiedMouseEvent) => void; }; type BrickRoadIndicatorIconProps = { From 5d8511c1346134a00bd221475c0fe027d78d60be Mon Sep 17 00:00:00 2001 From: KJ21-ENG Date: Tue, 19 May 2026 04:17:44 +0530 Subject: [PATCH 2/5] Fix targeted validation issues --- .../ListItem/TransactionListItem/index.tsx | 116 +++++++++++------- src/components/Search/SearchList/index.tsx | 2 +- src/components/Search/index.tsx | 2 +- .../SelectionList/ListItem/BaseListItem.tsx | 5 +- .../openInternalRouteInNewTab/index.native.ts | 15 +-- .../openInternalRouteInNewTab/index.ts | 11 +- src/libs/SearchUIUtils.ts | 4 +- src/pages/workspace/WorkspacesListPage.tsx | 6 +- 8 files changed, 92 insertions(+), 69 deletions(-) diff --git a/src/components/Search/SearchList/ListItem/TransactionListItem/index.tsx b/src/components/Search/SearchList/ListItem/TransactionListItem/index.tsx index b9b243cb2cef..c1479f48933a 100644 --- a/src/components/Search/SearchList/ListItem/TransactionListItem/index.tsx +++ b/src/components/Search/SearchList/ListItem/TransactionListItem/index.tsx @@ -210,50 +210,46 @@ function TransactionListItem({ }); }; - const sharedProps = { - item, - transactionItem, - isDeletedTransaction, - isFocused, - showTooltip, - isDisabled, - canSelectMultiple, - onSelectRow, - onCheckboxPress: onSelectionButtonPress, - onFocus, - onLongPressRow, - shouldSyncFocus, - columns, - isLoading, - isActionLoading, - transactionViolations, - handleActionButtonPress, - transactionPreviewData, - exportedReportActions, - nonPersonalAndWorkspaceCards, - isAttendeesEnabledForMovingPolicy, - shouldDisableHoverStyle, - onPressRow: handleOnPress, - onMouseDownRow: handleOnMouseDown, - onHoverInRow: handleOnHoverIn, - onEditDate, - onEditMerchant, - onEditDescription, - onEditCategory, - onEditAmount, - onEditTag, - canEditDate, - canEditMerchant, - canEditDescription, - canEditCategory, - canEditAmount, - canEditTag, - }; - if (!isLargeScreenWidth) { return ( @@ -262,7 +258,43 @@ function TransactionListItem({ return ( ); diff --git a/src/components/Search/SearchList/index.tsx b/src/components/Search/SearchList/index.tsx index 65ae99475413..0887ff2f5957 100644 --- a/src/components/Search/SearchList/index.tsx +++ b/src/components/Search/SearchList/index.tsx @@ -29,8 +29,8 @@ import useUndeleteTransactions from '@hooks/useUndeleteTransactions'; import useWindowDimensions from '@hooks/useWindowDimensions'; import {turnOnMobileSelectionMode} from '@libs/actions/MobileSelectionMode'; import DateUtils from '@libs/DateUtils'; -import navigationRef from '@libs/Navigation/navigationRef'; import type {ModifiedMouseEvent} from '@libs/Navigation/helpers/openInternalRouteInNewTab'; +import navigationRef from '@libs/Navigation/navigationRef'; import {applySelectionToItem, getTableMinWidth} from '@libs/SearchUIUtils'; import variables from '@styles/variables'; import type {TransactionPreviewData} from '@userActions/Search'; diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index 9f3112a9f339..9351a538ffd6 100644 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -34,9 +34,9 @@ import type {TransactionPreviewData} from '@libs/actions/Search'; import {setOptimisticDataForTransactionThreadPreview} from '@libs/actions/Search'; import {flushDeferredWrite, hasDeferredWrite} from '@libs/deferredLayoutWrite'; import Log from '@libs/Log'; +import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute'; import openInternalRouteInNewTab, {isModifiedMousePress} from '@libs/Navigation/helpers/openInternalRouteInNewTab'; import type {ModifiedMouseEvent} from '@libs/Navigation/helpers/openInternalRouteInNewTab'; -import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTopmostFullScreenRoute'; import type {PlatformStackNavigationProp} from '@libs/Navigation/PlatformStackNavigation/types'; import TransitionTracker from '@libs/Navigation/TransitionTracker'; import {isCreatedTaskReportAction} from '@libs/ReportActionsUtils'; diff --git a/src/components/SelectionList/ListItem/BaseListItem.tsx b/src/components/SelectionList/ListItem/BaseListItem.tsx index c507255a70e5..c03fa8b4b63d 100644 --- a/src/components/SelectionList/ListItem/BaseListItem.tsx +++ b/src/components/SelectionList/ListItem/BaseListItem.tsx @@ -176,7 +176,7 @@ function BaseListItem({ > ({ onFocus={onFocus} role={role} tabIndex={tabIndex} - {...accessibleAndAccessibilityLabel} + accessible={accessibleAndAccessibilityLabel.accessible} + accessibilityLabel={accessibleAndAccessibilityLabel.accessibilityLabel ?? ''} accessibilityState={accessibilityState} aria-current={ariaCurrent} onMouseLeave={handleMouseLeave} diff --git a/src/libs/Navigation/helpers/openInternalRouteInNewTab/index.native.ts b/src/libs/Navigation/helpers/openInternalRouteInNewTab/index.native.ts index 5adea91a6a6f..beb2ae56acbc 100644 --- a/src/libs/Navigation/helpers/openInternalRouteInNewTab/index.native.ts +++ b/src/libs/Navigation/helpers/openInternalRouteInNewTab/index.native.ts @@ -7,21 +7,12 @@ type ModifiedMouseEvent = { ctrlKey?: boolean; button?: number; key?: string; - nativeEvent?: { - metaKey?: boolean; - ctrlKey?: boolean; - button?: number; - key?: string; - }; + nativeEvent?: unknown; }; -function isModifiedMousePress(_event?: ModifiedMouseEvent) { - return false; -} +const isModifiedMousePress: (event?: ModifiedMouseEvent) => boolean = () => false; -function openInternalRouteInNewTab(_route: Route, _event?: ModifiedMouseEvent) { - return false; -} +const openInternalRouteInNewTab: (route: Route, event?: ModifiedMouseEvent) => boolean = () => false; export {isModifiedMousePress}; export type {ModifiedMouseEvent}; diff --git a/src/libs/Navigation/helpers/openInternalRouteInNewTab/index.ts b/src/libs/Navigation/helpers/openInternalRouteInNewTab/index.ts index 5fd5a595cfd3..a4f73367c397 100644 --- a/src/libs/Navigation/helpers/openInternalRouteInNewTab/index.ts +++ b/src/libs/Navigation/helpers/openInternalRouteInNewTab/index.ts @@ -7,20 +7,17 @@ type ModifiedMouseEvent = { ctrlKey?: boolean; button?: number; key?: string; - nativeEvent?: { - metaKey?: boolean; - ctrlKey?: boolean; - button?: number; - key?: string; - }; + nativeEvent?: unknown; }; +type ModifiedMouseEventSource = Pick; + function getRouteURL(route: Route) { return new URL(route.startsWith('/') ? route : `/${route}`, window.location.origin).toString(); } function isModifiedMousePress(event?: ModifiedMouseEvent) { - const mouseEvent = event?.nativeEvent ?? event; + const mouseEvent = event?.nativeEvent && typeof event.nativeEvent === 'object' ? (event.nativeEvent as ModifiedMouseEventSource) : event; if (!mouseEvent || 'key' in mouseEvent) { return false; diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 2af667195222..1144b0f960a9 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -2545,7 +2545,9 @@ function createAndOpenSearchTransactionThread( const shouldNavigateToTransactionThread = (!isFromOneTransactionReport || isFromSelfDM || isDeleted) && transactionThreadReport?.reportID !== CONST.REPORT.UNREPORTED_REPORT_ID; // When we have an actual transaction thread (childReportID from Onyx) but the report isn't in Onyx yet // (e.g. Search didn't return the IOU action for deleted items), use childReportID directly so we don't navigate with undefined - const targetReportID = shouldNavigateToTransactionThread ? (transactionThreadReport?.reportID ?? (hasActualTransactionThread ? iouReportAction.childReportID : undefined)) : item.reportID; + const targetReportID = shouldNavigateToTransactionThread + ? (transactionThreadReport?.reportID ?? (hasActualTransactionThread ? iouReportAction.childReportID : undefined)) + : item.reportID; if (shouldNavigate && targetReportID) { Navigation.setNavigationActionToMicrotaskQueue(() => Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID: targetReportID, backTo}))); diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx index 1cf05017c79c..1d54d6df4dc2 100755 --- a/src/pages/workspace/WorkspacesListPage.tsx +++ b/src/pages/workspace/WorkspacesListPage.tsx @@ -511,11 +511,11 @@ function WorkspacesListPage() { }; const navigateToWorkspace = (policyID: string, event?: ModifiedMouseEvent) => { - const route = shouldUseNarrowLayout ? ROUTES.WORKSPACE_INITIAL.getRoute(policyID) : ROUTES.WORKSPACE_OVERVIEW.getRoute(policyID); - if (openInternalRouteInNewTab(route, event)) { + const workspaceRoute = shouldUseNarrowLayout ? ROUTES.WORKSPACE_INITIAL.getRoute(policyID) : ROUTES.WORKSPACE_OVERVIEW.getRoute(policyID); + if (openInternalRouteInNewTab(workspaceRoute, event)) { return; } - Navigation.navigate(route); + Navigation.navigate(workspaceRoute); }; const navigateToDomain = ({domainAccountID, isAdmin}: {domainAccountID: number; isAdmin: boolean}) => { From e7eb9aa6e96270d0bd970cdbd443816763f725d7 Mon Sep 17 00:00:00 2001 From: KJ21-ENG Date: Tue, 19 May 2026 04:28:03 +0530 Subject: [PATCH 3/5] Record final validation From c74b0312597071519b156a8a71b91e90a0c5ff08 Mon Sep 17 00:00:00 2001 From: KJ21-ENG Date: Tue, 19 May 2026 12:07:14 +0530 Subject: [PATCH 4/5] Address review consistency comments --- .../ListItem/TransactionListItem/index.tsx | 116 +++++++----------- .../openInternalRouteInNewTab/index.native.ts | 11 +- .../openInternalRouteInNewTab/index.ts | 13 +- .../openInternalRouteInNewTab/types.ts | 13 ++ 4 files changed, 57 insertions(+), 96 deletions(-) create mode 100644 src/libs/Navigation/helpers/openInternalRouteInNewTab/types.ts diff --git a/src/components/Search/SearchList/ListItem/TransactionListItem/index.tsx b/src/components/Search/SearchList/ListItem/TransactionListItem/index.tsx index c1479f48933a..b9b243cb2cef 100644 --- a/src/components/Search/SearchList/ListItem/TransactionListItem/index.tsx +++ b/src/components/Search/SearchList/ListItem/TransactionListItem/index.tsx @@ -210,46 +210,50 @@ function TransactionListItem({ }); }; + const sharedProps = { + item, + transactionItem, + isDeletedTransaction, + isFocused, + showTooltip, + isDisabled, + canSelectMultiple, + onSelectRow, + onCheckboxPress: onSelectionButtonPress, + onFocus, + onLongPressRow, + shouldSyncFocus, + columns, + isLoading, + isActionLoading, + transactionViolations, + handleActionButtonPress, + transactionPreviewData, + exportedReportActions, + nonPersonalAndWorkspaceCards, + isAttendeesEnabledForMovingPolicy, + shouldDisableHoverStyle, + onPressRow: handleOnPress, + onMouseDownRow: handleOnMouseDown, + onHoverInRow: handleOnHoverIn, + onEditDate, + onEditMerchant, + onEditDescription, + onEditCategory, + onEditAmount, + onEditTag, + canEditDate, + canEditMerchant, + canEditDescription, + canEditCategory, + canEditAmount, + canEditTag, + }; + if (!isLargeScreenWidth) { return ( @@ -258,43 +262,7 @@ function TransactionListItem({ return ( ); diff --git a/src/libs/Navigation/helpers/openInternalRouteInNewTab/index.native.ts b/src/libs/Navigation/helpers/openInternalRouteInNewTab/index.native.ts index beb2ae56acbc..0aabc269d12a 100644 --- a/src/libs/Navigation/helpers/openInternalRouteInNewTab/index.native.ts +++ b/src/libs/Navigation/helpers/openInternalRouteInNewTab/index.native.ts @@ -1,14 +1,5 @@ import type {Route} from '@src/ROUTES'; - -type ModifiedMouseEvent = { - preventDefault?: () => void; - stopPropagation?: () => void; - metaKey?: boolean; - ctrlKey?: boolean; - button?: number; - key?: string; - nativeEvent?: unknown; -}; +import type {ModifiedMouseEvent} from './types'; const isModifiedMousePress: (event?: ModifiedMouseEvent) => boolean = () => false; diff --git a/src/libs/Navigation/helpers/openInternalRouteInNewTab/index.ts b/src/libs/Navigation/helpers/openInternalRouteInNewTab/index.ts index a4f73367c397..05e143c98837 100644 --- a/src/libs/Navigation/helpers/openInternalRouteInNewTab/index.ts +++ b/src/libs/Navigation/helpers/openInternalRouteInNewTab/index.ts @@ -1,16 +1,5 @@ import type {Route} from '@src/ROUTES'; - -type ModifiedMouseEvent = { - preventDefault?: () => void; - stopPropagation?: () => void; - metaKey?: boolean; - ctrlKey?: boolean; - button?: number; - key?: string; - nativeEvent?: unknown; -}; - -type ModifiedMouseEventSource = Pick; +import type {ModifiedMouseEvent, ModifiedMouseEventSource} from './types'; function getRouteURL(route: Route) { return new URL(route.startsWith('/') ? route : `/${route}`, window.location.origin).toString(); diff --git a/src/libs/Navigation/helpers/openInternalRouteInNewTab/types.ts b/src/libs/Navigation/helpers/openInternalRouteInNewTab/types.ts new file mode 100644 index 000000000000..f1c30410b64e --- /dev/null +++ b/src/libs/Navigation/helpers/openInternalRouteInNewTab/types.ts @@ -0,0 +1,13 @@ +type ModifiedMouseEvent = { + preventDefault?: () => void; + stopPropagation?: () => void; + metaKey?: boolean; + ctrlKey?: boolean; + button?: number; + key?: string; + nativeEvent?: unknown; +}; + +type ModifiedMouseEventSource = Pick; + +export type {ModifiedMouseEvent, ModifiedMouseEventSource}; From d7c3c2ada005fbb8dd91c0dfd8dec6a6eedaf1e4 Mon Sep 17 00:00:00 2001 From: KJ21-ENG Date: Tue, 19 May 2026 20:30:28 +0530 Subject: [PATCH 5/5] Revert unnecessary BaseListItem prop expansion --- src/components/SelectionList/ListItem/BaseListItem.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/SelectionList/ListItem/BaseListItem.tsx b/src/components/SelectionList/ListItem/BaseListItem.tsx index c03fa8b4b63d..c507255a70e5 100644 --- a/src/components/SelectionList/ListItem/BaseListItem.tsx +++ b/src/components/SelectionList/ListItem/BaseListItem.tsx @@ -176,7 +176,7 @@ function BaseListItem({ > ({ onFocus={onFocus} role={role} tabIndex={tabIndex} - accessible={accessibleAndAccessibilityLabel.accessible} - accessibilityLabel={accessibleAndAccessibilityLabel.accessibilityLabel ?? ''} + {...accessibleAndAccessibilityLabel} accessibilityState={accessibilityState} aria-current={ariaCurrent} onMouseLeave={handleMouseLeave}