Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
1d3cdc4
feat: add shift+click range selection to the Reports and MoneyRequest…
TaduJR Aug 10, 2026
cdd5fe3
Merge branch 'main' of https://github.com/TaduJR/App into feat/90539-…
TaduJR Aug 10, 2026
adc86b1
chore: apply plain comment style
TaduJR Aug 10, 2026
6214103
fix: rename isAnchorable to canAnchor
TaduJR Aug 10, 2026
54ddf2e
fix: allow deselecting one child of a group selected before its child…
TaduJR Aug 10, 2026
11cd7f0
fix: share one group-selected check and stop layout flips collapsing …
TaduJR Aug 10, 2026
ab0262b
fix: let shift+click narrow a group selected before its children loaded
TaduJR Aug 10, 2026
7df7370
fix: split the engine's selected check into anchoring and protection
TaduJR Aug 10, 2026
e1f3103
fix: only unregister group children when leaving the split layout
TaduJR Aug 10, 2026
1395126
fix: let a cold range adopt a block selection so it can narrow all of it
TaduJR Aug 11, 2026
16622a6
Merge branch 'main' of https://github.com/TaduJR/App into feat/90539-…
TaduJR Aug 11, 2026
9ce8f5d
refactor: build Search list test fixtures without type casts
TaduJR Aug 11, 2026
238f477
fix: spell out a group selection before adding its children to a range
TaduJR Aug 11, 2026
598ff51
fix: refresh stale group children and keep paging groups whole
TaduJR Aug 12, 2026
153048c
fix: keep a closed group's rows out of shift+click ranges
TaduJR Aug 12, 2026
e416726
Merge branch 'main' of https://github.com/TaduJR/App into feat/90539-…
TaduJR Aug 12, 2026
1bd909a
chore: drop comments that restate the code
TaduJR Aug 12, 2026
c4b407e
chore: document the shift+click hook params and args
TaduJR Aug 12, 2026
c6e054f
fix: seed a lazy group's range once its rows arrive
TaduJR Aug 12, 2026
46dcc48
fix: treat a re-resolved anchor as a fresh session
TaduJR Aug 12, 2026
bcf9d68
fix: stop a header selection counting as hand-picked
TaduJR Aug 12, 2026
9fae8b2
refactor: clear the pending seed in one place and drop an unused return
TaduJR Aug 12, 2026
bf2d32d
fix: keep select-all-matching through a range narrowing
TaduJR Aug 12, 2026
6d20051
refactor: give the shift-range group registry one owner per fact
TaduJR Aug 12, 2026
d02cdfb
fix: record an exclusion when a deselected row has no entry to remove
TaduJR Aug 12, 2026
532d46f
fix: keep a lazy group's selected children through a data refresh
TaduJR Aug 12, 2026
9df50c5
fix: clear select-all-matching when the header checkbox clears the se…
TaduJR Aug 12, 2026
58ada4b
fix: uncheck a group's child on screen, not just in the selection
TaduJR Aug 12, 2026
e128d52
fix: make a group's rows and selection agree across every gesture
TaduJR Aug 12, 2026
da9495f
fix: answer 'is this row selected' with one predicate everywhere
TaduJR Aug 12, 2026
93ccce4
fix: stop one uncheck clearing the whole select-all-matching selection
TaduJR Aug 12, 2026
dcdbb3c
fix: drop the shift-range session when the search changes
TaduJR Aug 12, 2026
386d49b
fix: ask one predicate whether a group is checked
TaduJR Aug 13, 2026
2ea8528
fix: publish a group's rows only once its source has resolved
TaduJR Aug 13, 2026
3d001a6
fix: exclude a partly loaded group whole when its header is unchecked
TaduJR Aug 13, 2026
86e3213
refactor: split the selection provider and drop the casts around it
TaduJR Aug 13, 2026
9264771
fix: read the whole selection at one freshness in the write path
TaduJR Aug 13, 2026
2cf9c56
fix: give every group rule a single owner
TaduJR Aug 13, 2026
563528f
refactor: stop restating what the types already guarantee
TaduJR Aug 13, 2026
b4a8dd9
fix: resolve a seeded range block at the shift+click, not when it is …
TaduJR Aug 13, 2026
1776715
fix: stop a snapshot gap dropping the selection or the range session
TaduJR Aug 14, 2026
96f863f
fix: start the range where the click landed when the seeded block has…
TaduJR Aug 14, 2026
b9130cc
fix: key the search-scoped state on the query hash rather than the sn…
TaduJR Aug 14, 2026
e982a11
fix: keep the anchor when a seeded block turns out to hold no rows
TaduJR Aug 14, 2026
0eec5c5
fix: let a group's exclusion be the whole story for the rows undernea…
TaduJR Aug 14, 2026
517cdc6
refactor: take a range's rows from the list instead of a registry bes…
TaduJR Aug 14, 2026
d1ca4d4
fix: give a group's checkbox one derivation for both layouts
TaduJR Aug 14, 2026
bb0b9c4
fix: let a click land on a group whose rows are still paging in
TaduJR Aug 14, 2026
1b29872
refactor: give the exclusion arithmetic a name and stop the header re…
TaduJR Aug 14, 2026
0718964
Merge branch 'main' of https://github.com/TaduJR/App into feat/90539-…
TaduJR Aug 14, 2026
212dbc7
perf: stop a group opening from re-rendering every row
TaduJR Aug 15, 2026
2f76511
Merge branch 'main' of https://github.com/TaduJR/App into feat/90539-…
TaduJR Aug 15, 2026
22a0375
fix: make the exclusion prune independent of the order it visits rows in
TaduJR Aug 15, 2026
ce2db84
fix: keep a row checked when it is picked back out of an excluded group
TaduJR Aug 15, 2026
1f8ed2e
fix: skip the commit when a group has no row it can select
TaduJR Aug 15, 2026
4f13cdf
refactor: give the reconcile pass the same transaction lookups the wr…
TaduJR Aug 15, 2026
1dd2417
refactor: narrow this PR to shift+click range selection
TaduJR Aug 15, 2026
9612b22
fix: stop a range leaving rows marked as covered by a group it only p…
TaduJR Aug 15, 2026
9a93de1
perf: let a shift+click that writes nothing bail on identity like the…
TaduJR Aug 15, 2026
999e1c6
perf: skip the write when a range re-covers a row with an identical e…
TaduJR Aug 15, 2026
7ceca7b
Merge branch 'main' of https://github.com/TaduJR/App into feat/90539-…
TaduJR Aug 28, 2026
f1fca9a
fix: keep a group's checkbox and its click reading the same rows
TaduJR Aug 28, 2026
c6c5fd1
test: pin that a shift+click on a group header stays an ordinary click
TaduJR Aug 28, 2026
d5e54e4
docs: move the open-groups registry description to the top of its file
TaduJR Aug 28, 2026
e596966
perf: use the indexes already built instead of scanning past them
TaduJR Aug 28, 2026
9935764
test: cover the report list's shift+click, Select All's seed, and the…
TaduJR Aug 28, 2026
eb6dd46
refactor: count a group's writable rows once, where writing them out …
TaduJR Aug 28, 2026
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 @@ -54,7 +54,7 @@ type MoneyRequestReportTransactionItemProps = {
isSelectionModeEnabled: boolean;

/** Callback function triggered upon pressing a transaction checkbox. */
toggleTransaction: (transactionID: string) => void;
toggleTransaction: (transactionID: string, shiftKey?: boolean) => void;

/** Callback function triggered upon pressing a transaction. */
handleOnPress: (transactionID: string) => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ import MoneyRequestReportTransactionItem from './MoneyRequestReportTransactionIt
import MoneyRequestReportTransactionLongPressModal from './MoneyRequestReportTransactionLongPressModal';
import MoneyRequestReportUnifiedList from './MoneyRequestReportUnifiedList';
import SearchMoneyRequestReportEmptyState from './SearchMoneyRequestReportEmptyState';
import useReportTransactionShiftRange from './useReportTransactionShiftRange';

type TransactionWithOptionalHighlight = OnyxTypes.Transaction & {
/** Whether the transaction should be highlighted, when it is added to the report */
Expand Down Expand Up @@ -367,19 +368,6 @@ function MoneyRequestReportTransactionList({
useHandleSelectionMode(selectedTransactionIDs);
const isMobileSelectionModeEnabled = useMobileSelectionMode();

const toggleTransaction = useCallback(
(transactionID: string) => {
let newSelectedTransactionIDs = selectedTransactionIDs;
if (selectedTransactionIDs.includes(transactionID)) {
newSelectedTransactionIDs = selectedTransactionIDs.filter((t) => t !== transactionID);
} else {
newSelectedTransactionIDs = [...selectedTransactionIDs, transactionID];
}
setSelectedTransactions(newSelectedTransactionIDs);
},
[setSelectedTransactions, selectedTransactionIDs],
);

const isTransactionSelected = useCallback((transactionID: string) => selectedTransactionIDs.includes(transactionID), [selectedTransactionIDs]);

useFocusEffect(
Expand Down Expand Up @@ -434,9 +422,7 @@ function MoneyRequestReportTransactionList({

useEffect(() => {
clearSelectedTransactions(true);
// We don't want to run the effect on change of clearSelectedTransactions since it can cause an infinite loop.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [reportID]);
}, [reportID, clearSelectedTransactions]);

const [sortConfig, setSortConfig] = useState<SortedTransactions>({
sortBy: CONST.SEARCH.TABLE_COLUMNS.DATE,
Expand Down Expand Up @@ -579,12 +565,23 @@ function MoneyRequestReportTransactionList({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [resolvedTransactions, currentGroupBy, report?.reportID, report?.currency, localeCompare, shouldGroupTransactions]);

const visualOrderTransactionIDs = useMemo(() => {
if (!shouldGroupTransactions || groupedTransactions.length === 0) {
return sortedTransactions.filter((transaction) => !isTransactionPendingDelete(transaction)).map((transaction) => transaction.transactionID);
}
return groupedTransactions.flatMap((group) => group.transactions.filter((transaction) => !isTransactionPendingDelete(transaction)).map((transaction) => transaction.transactionID));
}, [groupedTransactions, sortedTransactions, shouldGroupTransactions]);
const visualOrderTransactions = useMemo(
() => (shouldGroupTransactions && groupedTransactions.length > 0 ? groupedTransactions.flatMap((group) => group.transactions) : resolvedTransactions),
[groupedTransactions, resolvedTransactions, shouldGroupTransactions],
);

const visualOrderTransactionIDs = useMemo(
() => visualOrderTransactions.filter((transaction) => !isTransactionPendingDelete(transaction)).map((transaction) => transaction.transactionID),
[visualOrderTransactions],
);

const {toggleTransaction, toggleGroup, toggleAll} = useReportTransactionShiftRange({
reportID,
transactions: visualOrderTransactions,
selectedTransactionIDs,
setSelectedTransactions,
clearSelectedTransactions,
});

// Primitive proxy for visualOrderTransactionIDs used as the effect dependency below.
// Other callers (e.g. TransactionDuplicateReview.onPreviewPressed) can write to the same
Expand Down Expand Up @@ -642,18 +639,9 @@ function MoneyRequestReportTransactionList({
if (!group) {
return;
}
const groupTransactionIDs = group.transactions.filter((t) => !isTransactionPendingDelete(t)).map((t) => t.transactionID);
const anySelected = groupTransactionIDs.some((id) => selectedTransactionIDs.includes(id));

let newSelectedTransactionIDs = selectedTransactionIDs;
if (anySelected) {
newSelectedTransactionIDs = selectedTransactionIDs.filter((id) => !groupTransactionIDs.includes(id));
} else {
newSelectedTransactionIDs = [...selectedTransactionIDs, ...groupTransactionIDs];
}
setSelectedTransactions(newSelectedTransactionIDs);
toggleGroup(group.transactions.filter((t) => !isTransactionPendingDelete(t)).map((t) => t.transactionID));
},
[groupedTransactions, selectedTransactionIDs, setSelectedTransactions],
[groupedTransactions, toggleGroup],
);

/**
Expand Down Expand Up @@ -911,13 +899,7 @@ function MoneyRequestReportTransactionList({
]}
>
<Checkbox
onPress={() => {
if (selectedTransactionIDs.length !== 0) {
clearSelectedTransactions(true);
} else {
setSelectedTransactions(transactionsWithoutPendingDelete.map((t) => t.transactionID));
}
}}
onPress={() => toggleAll(transactionsWithoutPendingDelete.map((t) => t.transactionID))}
accessibilityLabel={translate('accessibilityHints.selectAllTransactions')}
isIndeterminate={selectedTransactionIDs.length > 0 && selectedTransactionIDs.length !== transactionsWithoutPendingDelete.length}
isChecked={selectedTransactionIDs.length > 0 && selectedTransactionIDs.length === transactionsWithoutPendingDelete.length}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* The report list's selection gestures, so a click writes the selection and moves the range session together.
* Splitting them is how the two drift: a selection the session never saw is one a later shift+click cannot narrow.
*/
import useShiftRangeSelection from '@hooks/useShiftRangeSelection';

import {applyShiftRangeBatchToKeySet} from '@libs/shiftRangeSelection';
import {isTransactionPendingDelete} from '@libs/TransactionUtils';

import type * as OnyxTypes from '@src/types/onyx';

import {useEffect} from 'react';

type ReportTransactionShiftRangeParams = {
/** Dropping the session with it, since the list is reused for the next report and a transaction can be on both */
reportID: string | undefined;

/** In the order they render, which is the order a range spans */
transactions: OnyxTypes.Transaction[];

selectedTransactionIDs: string[];

setSelectedTransactions: (transactionIDs: string[]) => void;

/** Clearing goes through its own action rather than an empty write, so the hook takes it to own both branches */
clearSelectedTransactions: (shouldClearIDs: true) => void;
};

type ReportTransactionShiftRange = {
/** Extends a range when the click carried Shift, and toggles the one row otherwise */
toggleTransaction: (transactionID: string, shiftKey?: boolean) => void;

/** Toggles a whole group, and records it as the block the next shift+click may narrow */
toggleGroup: (groupTransactionIDs: string[]) => void;

/** Select All, and the clear that a second press means */
toggleAll: (selectableTransactionIDs: string[]) => void;
};

function useReportTransactionShiftRange({
reportID,
transactions,
selectedTransactionIDs,
setSelectedTransactions,
clearSelectedTransactions,
}: ReportTransactionShiftRangeParams): ReportTransactionShiftRange {
// The engine asks this per row while resolving an anchor, so the lookup has to be constant time.
const selectedTransactionIDsSet = new Set(selectedTransactionIDs);
const transactionsByID = new Map(transactions.map((transaction) => [transaction.transactionID, transaction]));

const rangeApi = useShiftRangeSelection<OnyxTypes.Transaction>({
items: transactions,
getItemKey: (transaction) => transaction.transactionID ?? null,
isItemSelected: (transaction) => selectedTransactionIDsSet.has(transaction.transactionID),
isDisabledItem: (transaction) => isTransactionPendingDelete(transaction),
onApplyRange: (batch) => setSelectedTransactions(applyShiftRangeBatchToKeySet(batch, selectedTransactionIDs, (transaction) => transaction.transactionID)),
});

useEffect(() => {
rangeApi.clearAnchor();
}, [reportID, rangeApi]);

const toggleTransaction = (transactionID: string, shiftKey?: boolean) => {
const item = transactionsByID.get(transactionID);
if (item && rangeApi.applyShiftClick(item, shiftKey)) {
return;
}
setSelectedTransactions(selectedTransactionIDsSet.has(transactionID) ? selectedTransactionIDs.filter((id) => id !== transactionID) : [...selectedTransactionIDs, transactionID]);
if (item) {
rangeApi.notifyAnchor(item);
}
};

const toggleGroup = (groupTransactionIDs: string[]) => {
const groupTransactionIDSet = new Set(groupTransactionIDs);
const anySelected = groupTransactionIDs.some((id) => selectedTransactionIDsSet.has(id));
setSelectedTransactions(anySelected ? selectedTransactionIDs.filter((id) => !groupTransactionIDSet.has(id)) : [...selectedTransactionIDs, ...groupTransactionIDs]);
if (anySelected) {
// Deselecting paints no block, so reset instead of leaving a stale span to collapse.
rangeApi.clearAnchor();
return;
}
// Just this block: seeding the whole selection would span unrelated rows and deselect them.
rangeApi.seedRangeFromSelection(groupTransactionIDs);
};

const toggleAll = (selectableTransactionIDs: string[]) => {
if (selectedTransactionIDs.length !== 0) {
clearSelectedTransactions(true);
rangeApi.clearAnchor();
return;
}
setSelectedTransactions(selectableTransactionIDs);
// A full-list block, so the next shift+click collapses the selection onto the span it lands in.
rangeApi.seedFullRange();
};

return {toggleTransaction, toggleGroup, toggleAll};
}

export default useReportTransactionShiftRange;
5 changes: 5 additions & 0 deletions src/components/Search/ExpenseGroupedSearchView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import type {SearchListItem} from './SearchList/ListItem/types';
import type {CommonSearchViewProps, TransactionViewExtras} from './searchViewProps';
import type {SearchQueryJSON, SelectedTransactions} from './types';

import {NO_OPEN_GROUPS} from './hooks/useOpenGroupsRegistry';
import useSearchListViewState from './hooks/useSearchListViewState';
import AnimatedExitRow from './primitives/AnimatedExitRow';
import SelectionTopBar from './primitives/SelectionTopBar';
Expand All @@ -29,6 +30,7 @@ import GroupChildrenContainer from './SearchList/ListItem/GroupChildrenContainer
import GroupHeader from './SearchList/ListItem/GroupHeader';
import TransactionGroupListItem from './SearchList/ListItem/TransactionGroupListItem';
import {isGroupChildrenContainerItem, isGroupHeaderItem} from './SearchList/ListItem/types';
import useOpenGroupsForShiftRange from './SearchList/ListItem/useOpenGroupsForShiftRange';
import SearchListViewLayout from './SearchListViewLayout';

type ExpenseGroupedSearchViewProps = CommonSearchViewProps & TransactionViewExtras;
Expand Down Expand Up @@ -134,6 +136,9 @@ function ExpenseGroupedSearchView({
return next;
});

// Only the split layout renders children as their own rows.
useOpenGroupsForShiftRange(shouldSplit ? expandedGroups : NO_OPEN_GROUPS);

const [visibleColumns] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM, {selector: columnsSelector});

const {
Expand Down
6 changes: 6 additions & 0 deletions src/components/Search/SearchContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
SearchRowSelectionActionsContext,
SearchSelectionActionsContext,
SearchSelectionContext,
SearchShiftRangeGroupsContext,
} from './SearchContextDefinitions';

// Lightweight public surface for search contexts.
Expand Down Expand Up @@ -43,6 +44,10 @@ function useSearchRowSelectionActions() {
return useContext(SearchRowSelectionActionsContext);
}

function useSearchShiftRangeGroups() {
return useContext(SearchShiftRangeGroupsContext);
}

export {
SearchQueryContext,
SearchQueryActionsContext,
Expand All @@ -57,4 +62,5 @@ export {
useSearchSelectionContext,
useSearchSelectionActions,
useSearchRowSelectionActions,
useSearchShiftRangeGroups,
};
12 changes: 12 additions & 0 deletions src/components/Search/SearchContextDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type {
SearchRowSelectionActionsValue,
SearchSelectionActionsValue,
SearchSelectionContextValue,
SearchShiftRangeGroupsActions,
} from './types';

// This file holds the bare React.createContext() calls so they can be imported by `@hooks/useOnyx`
Expand Down Expand Up @@ -62,6 +63,9 @@ const defaultSearchSelectionContext: SearchSelectionContextValue = {

const defaultSearchSelectionActions: SearchSelectionActionsValue = {
setSelectedTransactions: () => {},
getSelectedTransactions: () => defaultSearchSelectionContext.selectedTransactions,
getExcludedTransactions: () => defaultSearchSelectionContext.excludedTransactions,
getAreAllMatchingItemsSelected: () => defaultSearchSelectionContext.areAllMatchingItemsSelected,
applySelection: () => {},
setSelectedReports: () => {},
setCurrentSelectedTransactionReportID: () => {},
Expand All @@ -75,13 +79,20 @@ const defaultRowSelectionActions: SearchRowSelectionActionsValue = {
toggleAll: () => {},
};

const defaultSearchShiftRangeGroupsActions: SearchShiftRangeGroupsActions = {
addGroupToRange: () => {},
removeGroupFromRange: () => {},
registryGeneration: undefined,
};

const SearchQueryContext = React.createContext<SearchQueryContextValue>(defaultSearchQueryContext);
const SearchQueryActionsContext = React.createContext<SearchQueryActionsValue>(defaultSearchQueryActions);
const SearchResultsContext = React.createContext<SearchResultsContextValue>(defaultSearchResultsContext);
const SearchResultsActionsContext = React.createContext<SearchResultsActionsValue>(defaultSearchResultsActions);
const SearchSelectionContext = React.createContext<SearchSelectionContextValue>(defaultSearchSelectionContext);
const SearchSelectionActionsContext = React.createContext<SearchSelectionActionsValue>(defaultSearchSelectionActions);
const SearchRowSelectionActionsContext = React.createContext<SearchRowSelectionActionsValue>(defaultRowSelectionActions);
const SearchShiftRangeGroupsContext = React.createContext<SearchShiftRangeGroupsActions>(defaultSearchShiftRangeGroupsActions);

export {
EMPTY_TRANSACTIONS_BY_REPORT_ID,
Expand All @@ -92,4 +103,5 @@ export {
SearchSelectionContext,
SearchSelectionActionsContext,
SearchRowSelectionActionsContext,
SearchShiftRangeGroupsContext,
};
Original file line number Diff line number Diff line change
Expand Up @@ -352,9 +352,12 @@ function ExpenseReportListItemInner<TItem extends ListItem>({
shouldShowMarkAsDoneCopy,
]);

const handleSelectionButtonPress = useCallback(() => {
onSelectionButtonPress?.(reportItem as unknown as TItem);
}, [onSelectionButtonPress, reportItem]);
const handleSelectionButtonPress = useCallback(
(shiftKey?: boolean) => {
onSelectionButtonPress?.(item, undefined, shiftKey);
},
[onSelectionButtonPress, item],
);

const listItemPressableStyle = useMemo(
() => [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {useCurrencyListActions} from '@hooks/useCurrencyList';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';

import {getShiftKeyFromEvent} from '@libs/shiftRangeSelection';

import CONST from '@src/CONST';

import React from 'react';
Expand All @@ -24,7 +26,7 @@ function ExpenseReportListItemRowNarrow({item, onCheckboxPress = () => {}, canSe
<View style={[styles.flexRow, styles.alignItemsCenter, styles.gap3, styles.pt3]}>
{!!canSelectMultiple && (
<Checkbox
onPress={onCheckboxPress}
onPress={(event) => onCheckboxPress(getShiftKeyFromEvent(event))}
isChecked={isSelectAllChecked}
isIndeterminate={isIndeterminate}
containerStyle={styles.m0}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';

import getBase62ReportID from '@libs/getBase62ReportID';
import {getShiftKeyFromEvent} from '@libs/shiftRangeSelection';

import variables from '@styles/variables';

Expand Down Expand Up @@ -302,7 +303,7 @@ function ExpenseReportListItemRowWide({
<View style={[styles.flex1, styles.flexRow, styles.alignItemsCenter, styles.gap3]}>
{!!canSelectMultiple && (
<Checkbox
onPress={onCheckboxPress}
onPress={(event) => onCheckboxPress(getShiftKeyFromEvent(event))}
isChecked={isSelectAllChecked}
isIndeterminate={isIndeterminate}
containerStyle={styles.m0}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type {OnyxEntry} from 'react-native-onyx';
type ExpenseReportListItemRowNarrowProps = {
item: ExpenseReportListItemType;
canSelectMultiple?: boolean;
onCheckboxPress?: () => void;
onCheckboxPress?: (shiftKey?: boolean) => void;
isSelectAllChecked?: boolean;
isIndeterminate?: boolean;
isDisabledCheckbox?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ function TransactionGroupListExpandedImpl({
shouldUseNarrowLayout={!isLargeScreenWidth}
shouldShowCheckbox={!!canSelectMultiple}
checkboxSentryLabel={CONST.SENTRY_LABEL.SEARCH.EXPANDED_TRANSACTION_ROW_CHECKBOX}
onCheckboxPress={() => onSelectionButtonPress?.(transaction as ListItem)}
onCheckboxPress={(_transactionID, shiftKey) => onSelectionButtonPress?.(transaction as ListItem, undefined, shiftKey)}
columns={currentColumns}
onButtonPress={(event) => handleButtonPress(transaction, event)}
style={[styles.noBorderRadius, isLargeScreenWidth ? [styles.p3, styles.pv2, styles.tableRowHeight] : styles.p4, styles.flex1]}
Expand Down
Loading
Loading