fix(useSelectableCollection): focus previous tab stop on Shift+Tab instead of collection root#10247
fix(useSelectableCollection): focus previous tab stop on Shift+Tab instead of collection root#10247coolassassin wants to merge 1 commit into
Conversation
… out of a collection Avoids unnecessary focus transitions and re-renders in Table when exiting via Shift+Tab from internal tab stops.
|
Thanks for the interest, as you can see, there's a bit of complexity with moving focus ourselves. It'd be better if we can let the browser handle the focus movement. This will help it work with things like shadow dom and iframes. The approach I'd look into would be to solve the re-renders by reducing the props or context that change for the cells. Did you try something like that and run into problems? Can you submit an issue with reproduction instructions? |
|
@snowystinger Did the issue with details: From my perspective, the main issue is that the table collection lives in React and works with React components. If there were an option to provide a table model to the table as a data structure, it would be possible to build a much more efficient solution for data grids. On the other hand, the current table solution updates the entire state on any change by design. If there were a way to update only the necessary parts of the state, it would be much more performant even with the current approach, but that would require a fairly significant change and refactoring. So, this PR is just a small fix for a local issue, trying to keep everything as is. |
Summary
When Shift+Tab is pressed inside a collection with
allowsTabNavigation={false}(Table, ListBox, GridList, etc.),useSelectableCollectioncalledref.current.focus()on the collection root before the browser continued tabbing. That intermediate focus on the grid root is an unnecessary pivot — focus should move directly to the previous tab stop outside the collection.For RAC Table this caused extra focus transitions and downstream React re-renders (for example Row
useFocusRing({ within: true })when focus briefly lands on the grid root).The fix mirrors the forward-Tab branch: on Shift+Tab, when the previous external tab stop matches the collection boundary, we
preventDefaultandfocusWithoutScrollingthat element. Otherwise we fall back tocollectionRoot.focus()(nested collections, mobile submenu back button, etc.).Changed files:
packages/react-aria/src/selection/useSelectableCollection.tspackages/react-aria/test/selection/useSelectableCollection.test.jspackages/@adobe/react-spectrum/test/table/TableTests.jsGuards:
prev === prevFromRoot— direct exit only from the collection boundary as a single tab stopprevmust not belong to another[data-collection]outside the current root[role="dialog"]but outside the collection use the fallback pathstopPropagation()(ComboBox dev-mode)✅ Pull Request Checklist:
📝 Test Instructions:
Automated
The new test uses
fireEvent.keyDown(notuser.tab({ shift: true })) so it exercisesuseSelectableCollection's Tab handler — same caveat as GridList.test.js.Manual (Table / Storybook)
Regression
🧢 Your Project:
dxFeed (Devexperts) — internal Table built on react-aria-components (
Table,div-grid,focusMode="cell").