feat: Shift+Click Range Selection Not Supported in Multi-Select Lists#92155
Conversation
…ttonPress is undefined
|
@QichenZhu Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button] |
|
@codex review |
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
|
Codex Review: Didn't find any major issues. Delightful! ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
We have upstream bug on react-native-key-command for the above case. Should we remove it for now and make a second PR, or handle it first on upstream? |
|
@TaduJR I'm not sure this is relevant to the original issue, which only mentions Shift+Click. I strongly suggest writing the steps yourself to reflect the actual testing flow you did, as AI tends to generate excessive steps in seconds but testers need hours to execute them, which is not practical. |
Hmm I validate the test steps and use AI to cover cases as much as possible so we will not skip edge cases or introduce regressions, but I feel you on the long steps. But the case I flagged seems to be a valid one and might be flagged during QA because even on a folder structure macOS there is support for Shift + Arrow Down and react-native-key-command is not capturing it. That's why I raised the question. |
|
@TaduJR could you check with the internal team? This expands the scope. |
Sure, Thanks! |
|
I don't think we need to account for shift+arrow down. We don't support it on Expensify Classic. There is no expectation it will be supported here based on that. |
|
Just to mention. Also about Shift and Cmd usage to split select Screen.Recording.2026-06-02.at.3.00.11.PM.mov |
Thanks @twisterdotcom
In that case i suppose this is not supported also in OD right? |
…ature-Shift+Click-Range-Selection-Not-Supported-in-Multi-Select-Lists # Conflicts: # src/components/Search/index.tsx
|
Wow, we do support shift+cmd select in Classic, so... if we can support that here too, that would be ace. |
Nice. I will include it. This is why I was testing for various cases. I will update the tests. Heads up @QichenZhu Test Case might grow even more. Thanks 🙏! |
…ature-Shift+Click-Range-Selection-Not-Supported-in-Multi-Select-Lists # Conflicts: # src/pages/workspace/expensifyCard/WorkspaceExpensifyCardListPage.tsx
…ature-Shift+Click-Range-Selection-Not-Supported-in-Multi-Select-Lists
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d5d1838f1e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3a4ae64a0e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
|
@codex review |
|
Codex Review: Didn't find any major issues. What shall we delve into next? ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
@TaduJR agree it's good to test thoroughly, but here the AI focused on quantity rather than quality. For example, this PR has some refactoring so we want to make sure we won't introduce regressions on mobile platforms, but the test steps don't cover that. |
…ature-Shift+Click-Range-Selection-Not-Supported-in-Multi-Select-Lists
To get our idea fully. All the test cases covered are unique cases of the feature right?, so by quality means is it uniqueness or reduction of verbosity to make it shallow?
I deliberately removed the mobile test cases after your concern on the steps being long and since it was not related to the core feature and after testing locally. Should I bring it back? |
…ature-Shift+Click-Range-Selection-Not-Supported-in-Multi-Select-Lists
joekaufmanexpensify
left a comment
There was a problem hiding this comment.
Nice improvement 👍
@TaduJR what I mean by quality is focusing on what matters: the risky scenarios that help catch bugs. AI doesn't know what's actually important in our context, so it tends to cover everything evenly. But our energy and attention are limited, and working through many routine steps can desensitize us to the real risks.
Thanks for testing it locally, I'll do it too. Could you update the steps to reflect what you did and were recorded in your video and remove the rest? |
|
@TaduJR gentle bump |
Explanation of Change
This PR adds Excel-style shift+click range selection to every multi-select list in the app. Anywhere users see checkboxes next to rows — workspace settings, Spend search results, Insights groups, the in-report transaction list — they can now hold Shift and click a second row to select everything between the first click and the second. Shift+clicking again extends the range further, shrinks it back, or flips it across the original click — exactly how Excel, Gmail, Outlook, and Finder behave. Mobile selection (long-press to enter selection mode, then tap to add) is untouched, and Shift+Arrow keyboard extension is intentionally out of scope — Expensify Classic doesn't support it either.
Holding Cmd (or Ctrl) — additive range
Holding Cmd on Mac (or Ctrl on Windows/Linux) while shift+clicking adds the new range to the existing selection without deselecting the previous one. This matches Mac Finder, Excel, and Expensify Classic — useful for picking disjoint groups of rows in one pass. A subsequent plain Shift+Click replaces the last additive range, so the muscle memory works the same as in Excel.
What changed under the hood
A new hook —
src/hooks/useShiftRangeSelection.ts— owns the logic. The session state (anIdle | Anchored | Rangingdiscriminated union) lives in a singleuseRefso plain-click anchor updates don't trigger render churn in the consumer; transitions are computed by a puresessionReducerfunction and range computation by a purecomputeShiftRangefunction. Each list passes its visible rows in and gets back a small set of methods to call from its click handlers:useArrowKeyFocusManager'sonFocusedIndexChange), the list tells the hook so the next shift+click anchors from the right place;The hook is the only place that knows the rules — anchor resolution, range computation, skipping headers and disabled rows, shrinking ranges, reversing direction, additive vs replacing — so every list behaves the same way. The session is explicit and deterministic: it lives between shift+clicks and ends only when the consumer tells the hook a non-shift interaction happened.
Pure helpers and types — the modifier-event reader, the primitive-key batch applier, a small farthest-end-from-anchor utility, and the
Modifiers/ShiftRangeBatchtypes — live insrc/libs/shiftRangeSelection/. The hook file holds only React state.Where it shows up
Most workspace pages (Tags, Categories, Members, Taxes, Distance Rates, Per Diem, Report Fields, Expense Rules, Spend Rules → Card / Category, Room Members, Report Participants) get it by adding one prop to the shared
BaseSelectionListcomponent —onShiftRangeApply. Three lists with their own custom selection plumbing — the Spend search results (Expenses, Reports, and the Insights views like Top Categories and Top Merchants), the transaction list inside a single expense report (both flat and Group-by-Category / Tag), and the Workspace Expensify Card list — wire the hook directly with the same protocol.Group headers
In any grouped list (Spend grouped by Category / Tag / Merchant / Card / Month / etc., and the in-report grouped view), shift-clicking a group header extends the range through the whole group, not just to the header. The hook receives the farthest loaded child of that group as the actual target so the range covers both edges of the group plus the gap to the anchor. Collapsed groups (where children haven't loaded yet) fall back to the existing "select all in this group" toggle.
Helpers
Two small helpers ship alongside the hook in
@libs/shiftRangeSelection/so each consumer can wire up in a couple of lines:string[]/number[]) — used by most pages;Fixed Issues
$ #90539
PROPOSAL: #90539 (comment)
Tests
Setup
A. Workspace lists — basic shift+click range
Use Workspaces → [your workspace] → Tags as the representative page. Repeat on Categories, Members, Taxes, Distance rates, Per diem, Report fields, Expense rules, Spend rules → Card / Category, Room members, Report participants.
B. Workspace lists — deselect then shift+click (anchor stays in range)
C. Workspace lists — Select All then shift+click
D. Spend → Expenses (flat list)
E. Spend → Reports — flat list + opened report
F. Opened expense report — in-report transaction list
G. Spend → Insights → Top categories (also Top merchants)
H. Workspace Expensify Card list
I. Holding Cmd (or Ctrl) — additive multi-range selection
Verifies Shift+Cmd (Mac) / Shift+Ctrl (Windows/Linux). Best done on the Tags page where rows are uniform; repeat on Spend → Expenses to confirm the search list path too.
Offline tests
Same as tests
QA Steps
// TODO: These must be filled out, or the issue title must include "[No QA]."
Same as tests
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectioncanBeMissingparam foruseOnyxtoggleReportand notonIconClick)src/languages/*files and using the translation methodSTYLE.md) were followedAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.ScrollViewcomponent to make it scrollable when more elements are added to the page.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari
Macbook-Chrome.mp4