fix: Keyboard Navigation: Track Distance: Map start/stop points cannot be reordered by using a keyboard#85124
Conversation
…t be reordered by using a keyboard
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6648de7798
ℹ️ 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".
…d-Navigation-Track-Distance-Map-start/stop-points-cannot-be-reordered-by-using-a-keyboard
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppScreen.Recording.2026-03-14.at.15.34.58.mp4Android: mWeb ChromeScreen.Recording.2026-03-14.at.15.28.00.mp4iOS: HybridAppScreen.Recording.2026-03-14.at.15.30.26.mp4iOS: mWeb SafariScreen.Recording.2026-03-14.at.15.31.43.mp4MacOS: Chrome / SafariScreen.Recording.2026-03-14.at.15.11.42.mp4 |
|
@TaduJR Could you test this on native app to make sure this change does not cause any regression? |
…d-Navigation-Track-Distance-Map-start/stop-points-cannot-be-reordered-by-using-a-keyboard
|
@codex review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Screen.Recording.2026-03-14.at.15.20.47.movI can't start or stop dragging on the emulator. In your attachment, did you use a real device? |
|
Yes @huult, I used real device for Android, but iOS demo is emulator. Maybe you didn't hold the click to drag for longer time? |
| for (const el of node.current?.querySelectorAll<HTMLElement>(PRESSABLE_SELECTOR) ?? []) { | ||
| el.setAttribute('tabindex', '-1'); | ||
| } |
There was a problem hiding this comment.
| for (const el of node.current?.querySelectorAll<HTMLElement>(PRESSABLE_SELECTOR) ?? []) { | |
| el.setAttribute('tabindex', '-1'); | |
| } | |
| const root = node.current; | |
| if (!root) { | |
| return; | |
| } | |
| const pressables = root.querySelectorAll<HTMLElement>(PRESSABLE_SELECTOR); | |
| for (const pressable of pressables) { | |
| if (pressable.tabIndex === -1) { | |
| continue; | |
| } | |
| pressable.tabIndex = -1; | |
| } |
We should handle cases where node.current might be null and separate the logic for better clarity and maintainability.
There was a problem hiding this comment.
We intentionally use setAttribute('tabindex', '-1') instead of pressable.tabIndex = -1 to avoid triggering ESLint's no-param-reassign rule without needing a suppressor comment. The null case is already handled by optional chaining with ?? [] fallback.
| const innerPressable = node.current?.querySelector<HTMLElement>(PRESSABLE_SELECTOR); | ||
| if (innerPressable) { | ||
| innerPressable.click(); | ||
| e.preventDefault(); | ||
| e.stopPropagation(); | ||
| } |
There was a problem hiding this comment.
| const innerPressable = node.current?.querySelector<HTMLElement>(PRESSABLE_SELECTOR); | |
| if (innerPressable) { | |
| innerPressable.click(); | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| } | |
| const root = node.current; | |
| if (!root) { | |
| return; | |
| } | |
| const pressable = root.querySelector<HTMLElement>(PRESSABLE_SELECTOR); | |
| pressable?.click(); | |
| e.preventDefault(); | |
| e.stopPropagation(); |
Same thing above
There was a problem hiding this comment.
I think this would change behavior e.preventDefault() / e.stopPropagation() would run even when no inner pressable is found, swallowing Enter keys unnecessarily. The current version only stops propagation when the click actually happens.
|
@codex review |
|
Codex Review: Didn't find any major issues. Swish! ℹ️ 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". |
Explanation of Change
The
SortableItemwrapper hadtabIndex={-1}which overrode dnd-kit'stabIndex: 0from{...attributes}, making the sortable wrapper unfocusable by keyboard. This meant dnd-kit's already-configuredKeyboardSensor(Space to grab, Arrow keys to move, Space to drop) could never activate — focus would land on the innerMenuIteminstead, and Space would trigger navigation rather than drag.This change removes the
tabIndex={-1}override so the sortable wrapper becomes the single keyboard-focusable element per item. To prevent the double Tab stop (wrapper + inner MenuItem both attabIndex: 0), auseLayoutEffectsetstabindex="-1"on inner pressable elements after each render. Enter key is forwarded to the inner pressable via.click()so waypoint navigation still works when not dragging.Additionally, string literals
'Space','Escape', and'Enter'inDraggableListandSortableItemare replaced withCONST.KEYBOARD_SHORTCUTSreferences.Fixed Issues
$ #74764
PROPOSAL: #74764 (comment)
Tests
Prerequisites:
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
Screen_Recording_20260313_233110_Expensify.mp4
Android: mWeb Chrome
Screen_Recording_20260313_233513_Chrome.mp4
iOS: Native
iOS-Native.mp4
iOS: mWeb Safari
Screen.Recording.2026-03-13.at.11.37.07.at.night.mov
MacOS: Chrome / Safari
Mac-Chrome.mp4