fix(useGridState): prevent infinite loop restoring focus when no focusable row remains (#10241)
* fix: prevent infinite loop in useGridState focus restoration
When the focused row is removed from the collection, the focus-restoration
effect searches collection.rows for a non-disabled, non-headerrow row to
move focus to. The single loop walked forward to the end, then jumped back
to parentNode.index and decremented — but the next iteration's
`index < rows.length - 1` check sent it forward again, so when every row in
the search range was disabled (or a headerrow) it bounced between
parentNode.index and the end forever and never reached `index < 0`. This
hangs the main thread (e.g. a table whose only rows are disabled loading
skeletons while a row is focused).
Replace the bouncing loop with two bounded passes — forward from the start
index, then backward — mirroring the already-correct walk in useListState.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: guard empty rows in useGridState focus restoration
Start the forward scan at Math.max(0, index) so an empty rows array
(index === -1 from Math.min(..., rows.length - 1)) no longer reads
rows[-1], matching the old `while (index >= 0)` no-op.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test: focus restoration does not hang when no focusable row remains
Removing the focused row while every remaining row is disabled previously
infinite-looped in useGridState's focus restoration. Without the fix this
test hangs; with it, focus is cleared and the render completes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* chore: satisfy oxfmt formatting in regression test
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs: trim bug-history narration from focus-restoration comment
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Remove redundant comment
Co-authored-by: Robert Snow <snowystinger@gmail.com>
* refactor(useGridState): use strict equality for focus-restoration null check
newRow is only ever null or a GridNode (never undefined), so `=== null`
is equivalent to `== null` here. Addresses review feedback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Robert Snow <snowystinger@gmail.com>